From aa732257ce233cdb6589dc44c298ef275244ad20 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:15:37 -0700 Subject: [PATCH 01/23] ADFA-5088: Add per-row tooltip long-press to Preferences Move `tooltipTag` from DialogPreference onto IPreference so every leaf item - switches included, not just dialogs - can carry one. Replace the screen-wide long-press gesture (which showed one tag for the whole current screen) with a per-row RecyclerView long-press that resolves the exact row touched via PreferenceGroupAdapter.getItem(position), and add an equivalent per-row long-press for choice dialogs' checkbox/radio lists (which the existing decor-view walk skips, since their rows are recycled). This lands the plumbing only; no preference yet has more than the old coarse per-screen tag, so behavior is otherwise unchanged. Co-Authored-By: Claude Sonnet 5 --- .../activities/PreferencesActivity.kt | 23 --- .../fragments/IDEPreferencesFragment.kt | 178 ++++++++++-------- .../androidide/preferences/commonPrefExts.kt | 52 ++--- .../ChoiceBasedDialogPreference.kt | 103 ++++++---- .../preferences/DialogPreference.kt | 73 ++++--- .../androidide/preferences/IPreference.kt | 20 +- .../preferences/PreferenceChoices.kt | 96 +++++----- .../preferences/SimpleClickablePreference.kt | 26 ++- 8 files changed, 311 insertions(+), 260 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt index 925313d930..5641862d81 100755 --- a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt @@ -17,9 +17,6 @@ package com.itsaky.androidide.activities import android.os.Bundle -import android.view.GestureDetector -import android.view.HapticFeedbackConstants -import android.view.MotionEvent import android.view.View import androidx.core.graphics.Insets import androidx.core.os.BundleCompat @@ -29,7 +26,6 @@ import com.itsaky.androidide.R import com.itsaky.androidide.app.EdgeToEdgeIDEActivity import com.itsaky.androidide.databinding.ActivityPreferencesBinding import com.itsaky.androidide.fragments.IDEPreferencesFragment -import com.itsaky.androidide.idetooltips.TooltipManager import com.itsaky.androidide.preferences.PluginSettingsEntryPreference import com.itsaky.androidide.preferences.addRootPreferences import com.itsaky.androidide.preferences.pluginSettingsPreferences @@ -50,20 +46,6 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { */ private var contributedPreferences: List? = null - private val gestureDetector by lazy { - GestureDetector( - this, - object : GestureDetector.SimpleOnGestureListener() { - override fun onLongPress(e: MotionEvent) { - binding.root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - val currentFragment = supportFragmentManager.findFragmentById(binding.fragmentContainer.id) as? IDEPreferencesFragment - val tooltipTag = currentFragment?.getCurrentScreenTooltip() ?: "" - TooltipManager.showIdeCategoryTooltip(this@PreferencesActivity, binding.root, tooltipTag) - } - }, - ) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -162,11 +144,6 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { _binding = null } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - gestureDetector.onTouchEvent(ev) - return super.dispatchTouchEvent(ev) - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) contributedPreferences?.let { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt index f663568c15..06c7c7dfb9 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt @@ -18,92 +18,118 @@ package com.itsaky.androidide.fragments import android.os.Bundle +import android.view.HapticFeedbackConstants import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.preference.PreferenceCategory import androidx.preference.PreferenceGroup +import androidx.preference.PreferenceGroupAdapter import com.google.android.material.transition.MaterialSharedAxis -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVELOPER -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_EDITOR -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_EDITOR_XML -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GRADLE -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX +import com.itsaky.androidide.idetooltips.TooltipManager import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TOP import com.itsaky.androidide.preferences.IPreference import com.itsaky.androidide.preferences.IPreferenceGroup import com.itsaky.androidide.preferences.IPreferenceScreen +import com.itsaky.androidide.utils.onLongPress class IDEPreferencesFragment : BasePreferenceFragment() { - - private var children: List = emptyList() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - return super.onCreateView(inflater, container, savedInstanceState) - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - super.onCreatePreferences(savedInstanceState, rootKey) - - if (context == null) { - return - } - - @Suppress("DEPRECATION") - this.children = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() - - preferenceScreen.removeAll() - addChildren(this.children, preferenceScreen) - } - - private fun addChildren(children: List, pref: PreferenceGroup) { - for (child in children) { - val preference = child.onCreateView(requireContext()) - if (child is IPreferenceScreen) { - preference.fragment = IDEPreferencesFragment::class.java.name - preference.extras.putParcelableArrayList(EXTRA_CHILDREN, ArrayList(child.children)) - - - pref.addPreference(preference) - continue - } - - if (child is IPreferenceGroup) { - pref.addPreference(preference as PreferenceCategory) - addChildren(child.children, preference) - continue - } - - - pref.addPreference(preference) - } - } - - fun getCurrentScreenTooltip(): String { - val firstChildKey = children.firstOrNull()?.key - return when (firstChildKey) { - "idepref_configure" -> PREFS_TOP - "idepref_general_interface" -> PREFS_GENERAL - "idepref_editor_common" -> PREFS_EDITOR - "idepref_build_gradle" -> PREFS_GRADLE - "idepref_build_gradleCommands" -> PREFS_GRADLE - "ide.preferences.terminal.debugging" -> PREFS_TERMUX - "ide.prefs.developerOptions.debugging" -> PREFS_DEVELOPER - "idepref_xml_trimFinalNewLine" -> PREFS_EDITOR_XML - else -> PREFS_TOP - } - } - - companion object { - const val EXTRA_CHILDREN = "ide.preferences.fragment.children" - } + private var children: List = emptyList() + + /** Every preference in this screen, including nested categories' children, keyed by its key. */ + private var tooltipTagsByKey: Map = emptyMap() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + return super.onCreateView(inflater, container, savedInstanceState) + } + + override fun onCreatePreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + super.onCreatePreferences(savedInstanceState, rootKey) + + if (context == null) { + return + } + + @Suppress("DEPRECATION") + this.children = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() + this.tooltipTagsByKey = collectTooltipTags(this.children) + + preferenceScreen.removeAll() + addChildren(this.children, preferenceScreen) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + + listView.onLongPress { e -> + val row = listView.findChildViewUnder(e.x, e.y) ?: return@onLongPress + val position = listView.getChildAdapterPosition(row) + if (position == androidx.recyclerview.widget.RecyclerView.NO_POSITION) { + return@onLongPress + } + + val key = (listView.adapter as? PreferenceGroupAdapter)?.getItem(position)?.key ?: return@onLongPress + val tag = tooltipTagsByKey[key]?.takeIf { it.isNotEmpty() } ?: PREFS_TOP + + row.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + TooltipManager.showIdeCategoryTooltip(requireContext(), row, tag) + } + } + + private fun collectTooltipTags(children: List): Map { + val map = mutableMapOf() + + fun visit(items: List) { + for (item in items) { + map[item.key] = item.tooltipTag + if (item is IPreferenceGroup && item !is IPreferenceScreen) { + visit(item.children) + } + } + } + + visit(children) + return map + } + + private fun addChildren( + children: List, + pref: PreferenceGroup, + ) { + for (child in children) { + val preference = child.onCreateView(requireContext()) + if (child is IPreferenceScreen) { + preference.fragment = IDEPreferencesFragment::class.java.name + preference.extras.putParcelableArrayList(EXTRA_CHILDREN, ArrayList(child.children)) + + pref.addPreference(preference) + continue + } + + if (child is IPreferenceGroup) { + pref.addPreference(preference as PreferenceCategory) + addChildren(child.children, preference) + continue + } + + pref.addPreference(preference) + } + } + + companion object { + const val EXTRA_CHILDREN = "ide.preferences.fragment.children" + } } - diff --git a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt index 7bd59dd0bd..d803d5fe08 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt @@ -23,27 +23,31 @@ import kotlin.reflect.KMutableProperty0 internal abstract class PropertyBasedMultiChoicePreference : MultiChoicePreference() { - abstract fun getProperties(): Map> - - override fun getEntries(preference: Preference): Array { - val properties = getProperties() - val entries = Array(properties.size) { PreferenceChoices.Entry.EMPTY } - - var index = 0 - properties.forEach { (key, property) -> - entries[index] = PreferenceChoices.Entry(key, property.get(), property) - ++index - } - - return entries - } - - override fun onChoicesConfirmed( - preference: Preference, - entries: Array - ) { - entries.forEach { entry -> - uncheckedCast>(entry.data).set(entry.isChecked) - } - } -} \ No newline at end of file +abstract fun getProperties(): Map> + +/** Tooltip tags for individual entries, keyed by the same label used in [getProperties]. */ +open fun getEntryTooltipTags(): Map = emptyMap() + +override fun getEntries(preference: Preference): Array { + val properties = getProperties() + val entryTooltipTags = getEntryTooltipTags() + val entries = Array(properties.size) { PreferenceChoices.Entry.EMPTY } + + var index = 0 + properties.forEach { (key, property) -> + entries[index] = PreferenceChoices.Entry(key, property.get(), property, entryTooltipTags[key] ?: "") + ++index + } + + return entries +} + +override fun onChoicesConfirmed( + preference: Preference, + entries: Array +) { + entries.forEach { entry -> + uncheckedCast>(entry.data).set(entry.isChecked) + } +} +} diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt index 949502f147..a6c39a2369 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt @@ -18,8 +18,10 @@ package com.itsaky.androidide.preferences import androidx.annotation.CallSuper +import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.itsaky.androidide.idetooltips.TooltipManager /** * Base class for dialog preferences which allows users to choose from multiple items. Subclasses @@ -27,53 +29,74 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder * * @author Akash Yadav */ -abstract class ChoiceBasedDialogPreference : DialogPreference(), PreferenceChoices { +abstract class ChoiceBasedDialogPreference : + DialogPreference(), + PreferenceChoices { + private var choices = emptyArray() - private var choices = emptyArray() + final override fun onConfigureDialog( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + ) { + choices = getEntries(preference) - final override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { - choices = getEntries(preference) + val selections = BooleanArray(choices.size) { choices[it].isChecked } + onConfigureDialogChoices(preference, dialog, choices, selections) - val selections = BooleanArray(choices.size) { choices[it].isChecked } - onConfigureDialogChoices(preference, dialog, choices, selections) + dialog.setPositiveButton(android.R.string.ok) { dialogInterface, _ -> + dialogInterface.dismiss() + onChoicesConfirmed(preference, choices) + } - dialog.setPositiveButton(android.R.string.ok) { dialogInterface, _ -> - dialogInterface.dismiss() - onChoicesConfirmed(preference, choices) - } + dialog.setNegativeButton(android.R.string.cancel) { dialogInterface, _ -> + dialogInterface.dismiss() + onChoicesCancelled(preference) + } + } - dialog.setNegativeButton(android.R.string.cancel) { dialogInterface, _ -> - dialogInterface.dismiss() - onChoicesCancelled(preference) - } - } + // The choice list is a ListView, which applyLongPressRecursively() deliberately skips (its rows + // are recycled, so a static recursive walk can't reach them) - wire per-row long-press here + // instead, falling back to the dialog's own tag for entries with none of their own. + final override fun onDialogShown( + preference: Preference, + dialog: AlertDialog, + ) { + dialog.listView?.setOnItemLongClickListener { _, view, position, _ -> + val tag = choices.getOrNull(position)?.tooltipTag?.takeIf { it.isNotEmpty() } ?: tooltipTag + TooltipManager.showIdeCategoryTooltip(preference.context, view, tag) + true + } + } - @CallSuper - override fun onSelectionChanged( - preference: Preference, - entry: PreferenceChoices.Entry, - position: Int, - isSelected: Boolean - ) { - entry._isChecked = isSelected - } + @CallSuper + override fun onSelectionChanged( + preference: Preference, + entry: PreferenceChoices.Entry, + position: Int, + isSelected: Boolean, + ) { + entry._isChecked = isSelected + } - /** - * Configure the dialog choices. - */ - protected abstract fun onConfigureDialogChoices( - preference: Preference, - dialog: MaterialAlertDialogBuilder, - entries: Array, - selections: BooleanArray - ) + /** + * Configure the dialog choices. + */ + protected abstract fun onConfigureDialogChoices( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + entries: Array, + selections: BooleanArray, + ) - override fun onChoicesConfirmed(preference: Preference, entries: Array) { - } + override fun onChoicesConfirmed( + preference: Preference, + entries: Array, + ) { + } - override fun onChoicesCancelled(preference: Preference) {} + override fun onChoicesCancelled(preference: Preference) {} - final override fun onDialogCancelled(preference: Preference) { - onChoicesCancelled(preference) - } -} \ No newline at end of file + final override fun onDialogCancelled(preference: Preference) { + onChoicesCancelled(preference) + } +} diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt index 4aedd03ab2..5b594c1fed 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt @@ -17,6 +17,7 @@ package com.itsaky.androidide.preferences +import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.itsaky.androidide.idetooltips.TooltipManager @@ -29,41 +30,53 @@ import com.itsaky.androidide.utils.applyLongPressRecursively * @author Akash Yadav */ abstract class DialogPreference : SimplePreference() { + open val dialogTitle: Int + get() = this.title - open val dialogTitle: Int - get() = this.title + open val dialogMessage: Int? = null + open val dialogCancellable: Boolean = true - open val dialogMessage: Int? = null - open val dialogCancellable: Boolean = true - open val tooltipTag: String = "" + override fun onPreferenceClick(preference: Preference): Boolean { + val dialog = DialogUtils.newMaterialDialogBuilder(preference.context) + dialog.setTitle(this.dialogTitle) + dialogMessage?.let { dialog.setMessage(it) } + dialog.setCancelable(this.dialogCancellable) + dialog.setOnCancelListener { onDialogCancelled(preference) } + onConfigureDialog(preference, dialog) + val alertDialog = dialog.create() + alertDialog.show() - override fun onPreferenceClick(preference: Preference): Boolean { - val dialog = DialogUtils.newMaterialDialogBuilder(preference.context) - dialog.setTitle(this.dialogTitle) - dialogMessage?.let { dialog.setMessage(it) } - dialog.setCancelable(this.dialogCancellable) - dialog.setOnCancelListener { onDialogCancelled(preference) } - onConfigureDialog(preference, dialog) - val alertDialog = dialog.create() - alertDialog.show() + alertDialog.window?.decorView?.applyLongPressRecursively { + TooltipManager.showIdeCategoryTooltip(preference.context, it, tooltipTag) + true + } - alertDialog.window?.decorView?.applyLongPressRecursively { - TooltipManager.showIdeCategoryTooltip(preference.context, it, tooltipTag) - true - } + onDialogShown(preference, alertDialog) - return true - } + return true + } - protected open fun onConfigureDialog(preference: Preference, - dialog: MaterialAlertDialogBuilder) { - } + /** + * Called after the dialog is shown. Subclasses whose dialog content isn't covered by + * [applyLongPressRecursively] (e.g. a [android.widget.ListView] of choices, which it skips + * because its rows are recycled) can use this to wire up their own tooltip triggers. + */ + protected open fun onDialogShown( + preference: Preference, + dialog: AlertDialog, + ) {} - /** - * Called when the dialog is cancelled by the user (e.g. the system back button), as opposed to - * being dismissed by an action button. The default behaviour discards any unconfirmed changes, - * matching the dialog's cancel button. - */ - protected open fun onDialogCancelled(preference: Preference) { - } + protected open fun onConfigureDialog( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + ) { + } + + /** + * Called when the dialog is cancelled by the user (e.g. the system back button), as opposed to + * being dismissed by an action button. The default behaviour discards any unconfirmed changes, + * matching the dialog's cancel button. + */ + protected open fun onDialogCancelled(preference: Preference) { + } } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt index c82c385dab..9b397ee82d 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/IPreference.kt @@ -28,18 +28,20 @@ import kotlinx.parcelize.Parcelize * @author Akash Yadav */ abstract class IPreference : Parcelable { + /** Icon resource for this preference. */ + open val icon: Int? = null - /** Icon resource for this preference. */ - open val icon: Int? = null + /** Key that will be used to store the value of this preference in shared preferences. */ + abstract val key: String - /** Key that will be used to store the value of this preference in shared preferences. */ - abstract val key: String + /** The title of the preference. */ + abstract val title: Int - /** The title of the preference. */ - abstract val title: Int + /** The summary of the preference. */ + open val summary: Int? = null - /** The summary of the preference. */ - open val summary: Int? = null + /** Tag used to look up this preference's tooltip in the documentation database. */ + open val tooltipTag: String = "" - abstract fun onCreateView(context: Context): Preference + abstract fun onCreateView(context: Context): Preference } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt index 220ba8ad7c..4f0ecbe969 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt @@ -26,58 +26,66 @@ import androidx.preference.Preference * @author Akash Yadav */ interface PreferenceChoices { + /** + * Get the entries for this preference. + */ + fun getEntries(preference: Preference): Array - /** - * Get the entries for this preference. - */ - fun getEntries(preference: Preference): Array + /** + * Called when an item is selected from the single choice list. + * + * @param position The position of the selected item. + * @param isSelected Whether the item is selected. + */ + fun onSelectionChanged( + preference: Preference, + entry: Entry, + position: Int, + isSelected: Boolean, + ) - /** - * Called when an item is selected from the single choice list. - * - * @param position The position of the selected item. - * @param isSelected Whether the item is selected. - */ - fun onSelectionChanged(preference: Preference, entry: Entry, position: Int, isSelected: Boolean) + /** + * Called when the user confirms the selections. + * + * @param entries The entries. + */ + fun onChoicesConfirmed( + preference: Preference, + entries: Array, + ) - /** - * Called when the user confirms the selections. - * - * @param entries The entries. - */ - fun onChoicesConfirmed(preference: Preference, entries: Array) + /** + * Called when the user cancels the selections. + */ + fun onChoicesCancelled(preference: Preference) - /** - * Called when the user cancels the selections. - */ - fun onChoicesCancelled(preference: Preference) + /** + * Entry in [PreferenceChoices]. + * + * @property label The label for the entry. + * @property isChecked Whether the item is checked or not. + * @property data The data object for the value. + * @property tooltipTag Tag used to look up this entry's own tooltip, if it has one distinct + * from the dialog's tooltip. Empty when the entry has no tooltip of its own. + */ + data class Entry( + val label: CharSequence, + @Suppress("PropertyName") internal var _isChecked: Boolean, + val data: Any, + val tooltipTag: String = "", + ) { + val isChecked: Boolean + get() = _isChecked - /** - * Entry in [PreferenceChoices]. - * - * @property label The label for the entry. - * @property isChecked Whether the item is checked or not. - * @property data The data object for the value. - */ - data class Entry( - val label: CharSequence, - @Suppress("PropertyName") internal var _isChecked: Boolean, - val data: Any, - ) { - - val isChecked: Boolean - get() = _isChecked - - companion object { - - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - val EMPTY = Entry("", false, 0) - } - } + companion object { + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) + val EMPTY = Entry("", false, 0) + } + } } /** * Map this [PreferenceChoices.Entry] array to entry labels. */ internal val Array.labels: Array - get() = Array(size) { this[it].label } \ No newline at end of file + get() = Array(size) { this[it].label } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt index 4cf78bcf54..a0f84754e3 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/SimpleClickablePreference.kt @@ -28,17 +28,15 @@ import kotlinx.parcelize.Parcelize */ @Parcelize class SimpleClickablePreference -@JvmOverloads -constructor( - override val key: String, - override val title: Int, - override val summary: Int? = null, - override val icon: Int? = null, - @IgnoredOnParcel // The prefs do not refresh so the parcelizable object is not required, remove this and update the prefs building - private val onClick: ((Preference) -> Boolean)? = { false } -) : SimplePreference() { - - override fun onPreferenceClick(preference: Preference): Boolean { - return onClick?.let { it(preference) } ?: false - } -} + @JvmOverloads + constructor( + override val key: String, + override val title: Int, + override val summary: Int? = null, + override val icon: Int? = null, + override val tooltipTag: String = "", + @IgnoredOnParcel // The prefs do not refresh so the parcelizable object is not required, remove this and update the prefs building + private val onClick: ((Preference) -> Boolean)? = { false }, + ) : SimplePreference() { + override fun onPreferenceClick(preference: Preference): Boolean = onClick?.let { it(preference) } ?: false + } From 1896332fcb9b0f5faaf6fa89cebfaea5054d8097 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:15:52 -0700 Subject: [PATCH 02/23] ADFA-5088: Add per-item tooltip tag constants for Preferences Add a distinct TooltipTag constant for every Preferences screen row, switch, dialog, and dialog checkbox, replacing the handful of coarse per-screen tags every item under a screen used to share. Not yet wired to any preference item - that follows in later commits. Co-Authored-By: Claude Sonnet 5 --- .../androidide/idetooltips/TooltipTag.kt | 81 ++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index 02a0571d1f..03025ef19d 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -40,15 +40,90 @@ object TooltipTag { */ fun gradleTaskTooltipTag(taskPath: String): String = "gradle." + taskPath.removePrefix(":") - // General Preferences + // Preferences - top level rows (Configure category and its screens) const val PREFS_TOP = "prefs.top" - const val PREFS_EDITOR = "prefs.editor" const val PREFS_GENERAL = "prefs.general" + const val PREFS_EDITOR = "prefs.editor" const val PREFS_BUILD_RUN = "prefs.buildrun" const val PREFS_GRADLE = "prefs.gradle" const val PREFS_TERMUX = "prefs.termux" - const val PREFS_EDITOR_XML = "prefs.editor.xml" + const val PREFS_GIT = "prefs.git" + const val PREFS_PLUGIN_MANAGER = "prefs.pluginmanager" + const val PREFS_ABOUT = "prefs.about" + const val PREFS_DEVOPTIONS = "prefs.devoptions" const val PREFS_DEVELOPER = "prefs.developer" + + // Preferences - General screen + const val PREFS_GENERAL_UIMODE = "prefs.general.uimode" + const val PREFS_GENERAL_LANGUAGE = "prefs.general.language" + const val PREFS_GENERAL_OPENLAST = "prefs.general.openlast" + const val PREFS_GENERAL_CONFIRMOPEN = "prefs.general.confirmopen" + + // Preferences - Editor screen (Common + Java categories) + const val PREFS_EDITOR_FONTSIZE = "prefs.editor.fontsize" + const val PREFS_EDITOR_TABSIZE = "prefs.editor.tabsize" + const val PREFS_EDITOR_NONPRINTING = "prefs.editor.nonprinting" + const val PREFS_EDITOR_NONPRINTING_LEADING = "prefs.editor.nonprinting.leading" + const val PREFS_EDITOR_NONPRINTING_TRAILING = "prefs.editor.nonprinting.trailing" + const val PREFS_EDITOR_NONPRINTING_INNER = "prefs.editor.nonprinting.inner" + const val PREFS_EDITOR_NONPRINTING_EMPTYLINES = "prefs.editor.nonprinting.emptylines" + const val PREFS_EDITOR_NONPRINTING_LINEBREAKS = "prefs.editor.nonprinting.linebreaks" + const val PREFS_EDITOR_SOFTTAB = "prefs.editor.softtab" + const val PREFS_EDITOR_WORDWRAP = "prefs.editor.wordwrap" + const val PREFS_EDITOR_MAGNIFIER = "prefs.editor.magnifier" + const val PREFS_EDITOR_WORDBOUNDARIES = "prefs.editor.wordboundaries" + const val PREFS_EDITOR_MATCHCASE = "prefs.editor.matchcase" + const val PREFS_EDITOR_DELETELINES = "prefs.editor.deletelines" + const val PREFS_EDITOR_SMARTBACKSPACE = "prefs.editor.smartbackspace" + const val PREFS_EDITOR_STICKYSCROLL = "prefs.editor.stickyscroll" + const val PREFS_EDITOR_PINLINES = "prefs.editor.pinlines" + const val PREFS_EDITOR_GOOGLESTYLE = "prefs.editor.googlestyle" + + // Preferences - Editor > XML formatting options screen + const val PREFS_EDITOR_XML = "prefs.editor.xml" + const val PREFS_XML_TRIMFINALNEWLINE = "prefs.xml.trimfinalnewline" + const val PREFS_XML_INSERTFINALNEWLINE = "prefs.xml.insertfinalnewline" + const val PREFS_XML_SPLITATTRIBUTES = "prefs.xml.splitattributes" + const val PREFS_XML_JOINCDATALINES = "prefs.xml.joincdatalines" + const val PREFS_XML_JOINCOMMENTLINES = "prefs.xml.joincommentlines" + const val PREFS_XML_JOINCONTENTLINES = "prefs.xml.joincontentlines" + const val PREFS_XML_SPACEBEFORECLOSE = "prefs.xml.spacebeforeclose" + const val PREFS_XML_PRESERVEEMPTYCONTENT = "prefs.xml.preserveemptycontent" + const val PREFS_XML_PRESERVEATTRIBUTES = "prefs.xml.preserveattributes" + const val PREFS_XML_CLOSEBRACKET = "prefs.xml.closebracket" + const val PREFS_XML_TRIMWHITESPACE = "prefs.xml.trimwhitespace" + const val PREFS_XML_MAXLINEWIDTH = "prefs.xml.maxlinewidth" + const val PREFS_XML_PRESERVENEWLINES = "prefs.xml.preservenewlines" + const val PREFS_XML_SPLITATTRIBINDENT = "prefs.xml.splitattribindent" + const val PREFS_XML_EMPTYELEMENTS = "prefs.xml.emptyelements" + + // Preferences - Build & Run screen + const val PREFS_BUILDRUN_AUTOLAUNCH = "prefs.buildrun.autolaunch" + const val PREFS_BUILDRUN_FLAGS = "prefs.buildrun.flags" + const val PREFS_BUILDRUN_FLAGS_STACKTRACE = "prefs.buildrun.flags.stacktrace" + const val PREFS_BUILDRUN_FLAGS_INFO = "prefs.buildrun.flags.info" + const val PREFS_BUILDRUN_FLAGS_DEBUG = "prefs.buildrun.flags.debug" + const val PREFS_BUILDRUN_FLAGS_SCAN = "prefs.buildrun.flags.scan" + const val PREFS_BUILDRUN_FLAGS_WARNINGMODEALL = "prefs.buildrun.flags.warningmodeall" + const val PREFS_BUILDRUN_FLAGS_BUILDCACHE = "prefs.buildrun.flags.buildcache" + const val PREFS_BUILDRUN_FLAGS_OFFLINE = "prefs.buildrun.flags.offline" + + // Preferences - Terminal screen + const val PREFS_TERMUX_LOGLEVEL = "prefs.termux.loglevel" + const val PREFS_TERMUX_KEYLOGGING = "prefs.termux.keylogging" + const val PREFS_TERMUX_CRASHREPORTS = "prefs.termux.crashreports" + const val PREFS_TERMUX_SOFTKEYBOARD = "prefs.termux.softkeyboard" + const val PREFS_TERMUX_NOHARDKEYBOARD = "prefs.termux.nohardkeyboard" + const val PREFS_TERMUX_MARGIN = "prefs.termux.margin" + + // Preferences - Git screen + const val PREFS_GIT_USERNAME = "prefs.git.username" + const val PREFS_GIT_USEREMAIL = "prefs.git.useremail" + + // Preferences - Developer options screen + const val PREFS_DEVOPTIONS_DUMPLOGS = "prefs.devoptions.dumplogs" + const val PREFS_DEVOPTIONS_LOGSENDER = "prefs.devoptions.logsender" + const val PLUGIN_MANAGER = "plugin.manager" const val TEMPLATE_TABBED_ACTIVITY = "template.tabbed.activity" const val TEMPLATE_LEGACY_PROJECT = "template.legacy.project" From b2f729ad934f4999f660d868659370ce8db2b398 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:16:15 -0700 Subject: [PATCH 03/23] ADFA-5088: Tag General, Editor, Java, and XML preference items Give every item on the General screen, the Editor screen (Common and Java categories), and the Editor > XML formatting options sub-screen its own tooltip tag, including the previously untaggable switches and each checkbox in the "show non-printing characters" dialog. Co-Authored-By: Claude Sonnet 5 --- .../androidide/preferences/editorPrefExts.kt | 299 ++++++++++-------- .../androidide/preferences/generalPrefExts.kt | 263 +++++++-------- .../androidide/preferences/javaPrefExts.kt | 24 +- .../androidide/preferences/xmlPrefExts.kt | 284 +++++++++-------- 4 files changed, 456 insertions(+), 414 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt index d31daeb9d0..c23aebead3 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt @@ -44,207 +44,228 @@ import kotlin.reflect.KMutableProperty0 @Parcelize class EditorPreferencesScreen( - override val key: String = "idepref_editor", - override val title: Int = string.idepref_editor_title, - override val summary: Int? = string.idepref_editor_summary, - override val children: List = mutableListOf(), +override val key: String = "idepref_editor", +override val title: Int = string.idepref_editor_title, +override val summary: Int? = string.idepref_editor_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR, ) : IPreferenceScreen() { - init { - addPreference(CommonConfigurations()) - addPreference(JavaCodeConfigurations()) - addPreference(XMLPreferencesScreen()) - } +init { + addPreference(CommonConfigurations()) + addPreference(JavaCodeConfigurations()) + addPreference(XMLPreferencesScreen()) +} } @Parcelize private class CommonConfigurations( - override val key: String = "idepref_editor_common", - override val title: Int = string.idepref_editor_category_common, - override val children: List = mutableListOf(), +override val key: String = "idepref_editor_common", +override val title: Int = string.idepref_editor_category_common, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(TextSize()) - addPreference(TabSize()) - addPreference(NonPrintablePaintingFlags()) - addPreference(UseSoftTab()) - addPreference(WordWrap()) - addPreference(UseMagnifier()) - addPreference(UseICU()) - addPreference(DeleteEmptyLines()) - addPreference(DeleteTabs()) - addPreference(StickyScrollEnabled()) - addPreference(PinLineNumbersEnabled()) - addPreference(CompletionsMatchLower()) - } +init { + addPreference(TextSize()) + addPreference(TabSize()) + addPreference(NonPrintablePaintingFlags()) + addPreference(UseSoftTab()) + addPreference(WordWrap()) + addPreference(UseMagnifier()) + addPreference(UseICU()) + addPreference(DeleteEmptyLines()) + addPreference(DeleteTabs()) + addPreference(StickyScrollEnabled()) + addPreference(PinLineNumbersEnabled()) + addPreference(CompletionsMatchLower()) +} } @Parcelize private class TextSize( - override val key: String = FONT_SIZE, - override val title: Int = string.idepref_editor_fontsize_title, - override val summary: Int? = string.idepref_editor_fontsize_summary, - override val icon: Int? = drawable.ic_text_size, - override val dialogTitle: Int = string.title_change_text_size, - override val dialogMessage: Int? = string.msg_editor_font_size, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR, +override val key: String = FONT_SIZE, +override val title: Int = string.idepref_editor_fontsize_title, +override val summary: Int? = string.idepref_editor_fontsize_summary, +override val icon: Int? = drawable.ic_text_size, +override val dialogTitle: Int = string.title_change_text_size, +override val dialogMessage: Int? = string.msg_editor_font_size, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_FONTSIZE, ) : DialogPreference() { - override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { - val binding = LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(preference.context)) - var size = EditorPreferences.fontSize - if (size !in 6f..32f) { - size = 14f - } - binding.slider.value = kotlin.math.round(size) - binding.slider.setLabelFormatter { it.toString() } - - dialog.setView(binding.root) - dialog.setPositiveButton(android.R.string.ok) { iface, _ -> - iface.dismiss() - EditorPreferences.fontSize = binding.slider.value - } - dialog.setNegativeButton(android.R.string.cancel, null) - dialog.setNeutralButton(string.reset) { iface, _ -> - iface.dismiss() - EditorPreferences.fontSize = 14f - } - } +override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { + val binding = LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(preference.context)) + var size = EditorPreferences.fontSize + if (size !in 6f..32f) { + size = 14f + } + binding.slider.value = kotlin.math.round(size) + binding.slider.setLabelFormatter { it.toString() } + + dialog.setView(binding.root) + dialog.setPositiveButton(android.R.string.ok) { iface, _ -> + iface.dismiss() + EditorPreferences.fontSize = binding.slider.value + } + dialog.setNegativeButton(android.R.string.cancel, null) + dialog.setNeutralButton(string.reset) { iface, _ -> + iface.dismiss() + EditorPreferences.fontSize = 14f + } +} } @Parcelize private class UseSoftTab( - override val key: String = USE_SOFT_TAB, - override val title: Int = string.idepref_editor_useSoftTabs_title, - override val summary: Int? = string.idepref_editor_useSoftTabs_summary, - override val icon: Int? = drawable.ic_space, +override val key: String = USE_SOFT_TAB, +override val title: Int = string.idepref_editor_useSoftTabs_title, +override val summary: Int? = string.idepref_editor_useSoftTabs_summary, +override val icon: Int? = drawable.ic_space, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_SOFTTAB, ) : SwitchPreference(setValue = EditorPreferences::useSoftTab::set, - getValue = EditorPreferences::useSoftTab::get) +getValue = EditorPreferences::useSoftTab::get) @Parcelize private class TabSize( - override val key: String = TAB_SIZE, - override val title: Int = string.title_tab_size, - override val summary: Int? = string.msg_tab_size, - override val icon: Int? = drawable.ic_tab, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR, +override val key: String = TAB_SIZE, +override val title: Int = string.title_tab_size, +override val summary: Int? = string.msg_tab_size, +override val icon: Int? = drawable.ic_tab, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_TABSIZE, ) : SingleChoicePreference() { - @IgnoredOnParcel - private val tabSizes = intArrayOf(2, 4, 6, 8) - - override fun getEntries(preference: Preference): Array { - val currentTabSize = EditorPreferences.tabSize - return Array(tabSizes.size) { index -> - PreferenceChoices.Entry( - label = tabSizes[index].toString(), - _isChecked = currentTabSize == tabSizes[index], - data = tabSizes[index] - ) - } - } - - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - EditorPreferences.tabSize = (entry?.data as? Int?) ?: 4 - } +@IgnoredOnParcel +private val tabSizes = intArrayOf(2, 4, 6, 8) + +override fun getEntries(preference: Preference): Array { + val currentTabSize = EditorPreferences.tabSize + return Array(tabSizes.size) { index -> + PreferenceChoices.Entry( + label = tabSizes[index].toString(), + _isChecked = currentTabSize == tabSizes[index], + data = tabSizes[index] + ) + } +} + +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + EditorPreferences.tabSize = (entry?.data as? Int?) ?: 4 +} } @Parcelize private class NonPrintablePaintingFlags( - override val key: String = PRINTABLE_CHARS, - override val title: Int = string.idepref_editor_paintingflags_title, - override val summary: Int? = string.idepref_editor_paintingflags_summary, - override val icon: Int? = drawable.ic_drawing, +override val key: String = PRINTABLE_CHARS, +override val title: Int = string.idepref_editor_paintingflags_title, +override val summary: Int? = string.idepref_editor_paintingflags_summary, +override val icon: Int? = drawable.ic_drawing, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_NONPRINTING, ) : PropertyBasedMultiChoicePreference() { - override fun getProperties(): Map> { - return linkedMapOf( - "Leading" to EditorPreferences::drawLeadingWs, - "Trailing" to EditorPreferences::drawTrailingWs, - "Inner" to EditorPreferences::drawInnerWs, - "Empty lines" to EditorPreferences::drawEmptyLineWs, - "Line breaks" to EditorPreferences::drawLineBreak - ) - } +override fun getProperties(): Map> { + return linkedMapOf( + "Leading" to EditorPreferences::drawLeadingWs, + "Trailing" to EditorPreferences::drawTrailingWs, + "Inner" to EditorPreferences::drawInnerWs, + "Empty lines" to EditorPreferences::drawEmptyLineWs, + "Line breaks" to EditorPreferences::drawLineBreak + ) +} + +override fun getEntryTooltipTags(): Map { + return mapOf( + "Leading" to TooltipTag.PREFS_EDITOR_NONPRINTING_LEADING, + "Trailing" to TooltipTag.PREFS_EDITOR_NONPRINTING_TRAILING, + "Inner" to TooltipTag.PREFS_EDITOR_NONPRINTING_INNER, + "Empty lines" to TooltipTag.PREFS_EDITOR_NONPRINTING_EMPTYLINES, + "Line breaks" to TooltipTag.PREFS_EDITOR_NONPRINTING_LINEBREAKS + ) +} } @Parcelize private class WordWrap( - override val key: String = WORD_WRAP, - override val title: Int = string.idepref_editor_word_wrap_title, - override val summary: Int? = string.idepref_editor_word_wrap_summary, - override val icon: Int? = drawable.ic_wrap_text, +override val key: String = WORD_WRAP, +override val title: Int = string.idepref_editor_word_wrap_title, +override val summary: Int? = string.idepref_editor_word_wrap_summary, +override val icon: Int? = drawable.ic_wrap_text, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_WORDWRAP, ) : SwitchPreference(setValue = EditorPreferences::wordwrap::set, - getValue = EditorPreferences::wordwrap::get) +getValue = EditorPreferences::wordwrap::get) @Parcelize private class UseMagnifier( - override val key: String = USE_MAGNIFER, - override val title: Int = string.idepref_editor_use_magnifier_title, - override val summary: Int? = string.idepref_editor_use_magnifier_summary, - override val icon: Int? = drawable.ic_loupe, +override val key: String = USE_MAGNIFER, +override val title: Int = string.idepref_editor_use_magnifier_title, +override val summary: Int? = string.idepref_editor_use_magnifier_summary, +override val icon: Int? = drawable.ic_loupe, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_MAGNIFIER, ) : SwitchPreference(setValue = EditorPreferences::useMagnifier::set, - getValue = EditorPreferences::useMagnifier::get) +getValue = EditorPreferences::useMagnifier::get) @Parcelize private class CompletionsMatchLower( - override val key: String = COMPLETIONS_MATCH_LOWER, - override val title: Int = string.idepref_java_matchLower_title, - override val summary: Int? = string.idepref_java_matchLower_summary, - override val icon: Int? = drawable.ic_text_lower, +override val key: String = COMPLETIONS_MATCH_LOWER, +override val title: Int = string.idepref_java_matchLower_title, +override val summary: Int? = string.idepref_java_matchLower_summary, +override val icon: Int? = drawable.ic_text_lower, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_MATCHCASE, ) : SwitchPreference( - setValue = EditorPreferences::completionsMatchLower::set, - getValue = EditorPreferences::completionsMatchLower::get +setValue = EditorPreferences::completionsMatchLower::set, +getValue = EditorPreferences::completionsMatchLower::get ) @Parcelize private class UseICU( - override val key: String = USE_ICU, - override val title: Int = string.idepref_useIcu_title, - override val summary: Int? = string.idepref_useIcu_summary, - override val icon: Int? = drawable.ic_expand_selection, +override val key: String = USE_ICU, +override val title: Int = string.idepref_useIcu_title, +override val summary: Int? = string.idepref_useIcu_summary, +override val icon: Int? = drawable.ic_expand_selection, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_WORDBOUNDARIES, ) : SwitchPreference(setValue = EditorPreferences::useIcu::set, - getValue = EditorPreferences::useIcu::get) +getValue = EditorPreferences::useIcu::get) @Parcelize private class DeleteEmptyLines( - override val key: String = DELETE_EMPTY_LINES, - override val title: Int = R.string.idepref_deleteEmptyLines_title, - override val summary: Int? = R.string.idepref_deleteEmptyLines_summary, - override val icon: Int? = drawable.ic_backspace +override val key: String = DELETE_EMPTY_LINES, +override val title: Int = R.string.idepref_deleteEmptyLines_title, +override val summary: Int? = R.string.idepref_deleteEmptyLines_summary, +override val icon: Int? = drawable.ic_backspace, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_DELETELINES, ) : SwitchPreference(setValue = EditorPreferences::deleteEmptyLines::set, - getValue = EditorPreferences::deleteEmptyLines::get) +getValue = EditorPreferences::deleteEmptyLines::get) @Parcelize private class DeleteTabs( - override val key: String = DELETE_TABS_ON_BACKSPACE, - override val title: Int = R.string.idepref_deleteTabs_title, - override val summary: Int? = R.string.idepref_deleteTabs_summary, - override val icon: Int? = drawable.ic_backspace +override val key: String = DELETE_TABS_ON_BACKSPACE, +override val title: Int = R.string.idepref_deleteTabs_title, +override val summary: Int? = R.string.idepref_deleteTabs_summary, +override val icon: Int? = drawable.ic_backspace, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_SMARTBACKSPACE, ) : - SwitchPreference(setValue = EditorPreferences::deleteTabsOnBackspace::set, - getValue = EditorPreferences::deleteTabsOnBackspace::get) +SwitchPreference(setValue = EditorPreferences::deleteTabsOnBackspace::set, + getValue = EditorPreferences::deleteTabsOnBackspace::get) @Parcelize private class StickyScrollEnabled( - override val key: String = STICKY_SCROLL_ENABLED, - override val title: Int = R.string.idepref_editor_stickScroll_title, - override val summary: Int? = R.string.idepref_editor_stickyScroll_summary, - override val icon: Int? = drawable.ic_sticky_scroll +override val key: String = STICKY_SCROLL_ENABLED, +override val title: Int = R.string.idepref_editor_stickScroll_title, +override val summary: Int? = R.string.idepref_editor_stickyScroll_summary, +override val icon: Int? = drawable.ic_sticky_scroll, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_STICKYSCROLL, ) : SwitchPreference(setValue = EditorPreferences::stickyScrollEnabled::set, - getValue = EditorPreferences::stickyScrollEnabled::get) +getValue = EditorPreferences::stickyScrollEnabled::get) @Parcelize private class PinLineNumbersEnabled( - override val key: String = PIN_LINE_NUMBERS, - override val title: Int = R.string.idepref_editor_pinLineNumbers_title, - override val summary: Int? = R.string.idepref_editor_pinLineNumbers_summary, - override val icon: Int? = drawable.ic_pin +override val key: String = PIN_LINE_NUMBERS, +override val title: Int = R.string.idepref_editor_pinLineNumbers_title, +override val summary: Int? = R.string.idepref_editor_pinLineNumbers_summary, +override val icon: Int? = drawable.ic_pin, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_PINLINES, ) : SwitchPreference(setValue = EditorPreferences::pinLineNumbers::set, - getValue = EditorPreferences::pinLineNumbers::get) +getValue = EditorPreferences::pinLineNumbers::get) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt index bdd2c517ec..1ccd6c595f 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt @@ -23,6 +23,10 @@ import androidx.core.content.ContextCompat import androidx.preference.Preference import com.itsaky.androidide.R import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_CONFIRMOPEN +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_LANGUAGE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_OPENLAST +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GENERAL_UIMODE import com.itsaky.androidide.preferences.internal.GeneralPreferences import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string @@ -32,183 +36,186 @@ import kotlinx.parcelize.Parcelize @Parcelize class GeneralPreferencesScreen( - override val key: String = "idepref_general", - override val title: Int = string.title_general, - override val summary: Int? = string.idepref_general_summary, - override val children: List = mutableListOf() +override val key: String = "idepref_general", +override val title: Int = string.title_general, +override val summary: Int? = string.idepref_general_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_GENERAL, ) : IPreferenceScreen() { - init { - addPreference(InterfaceConfig()) - addPreference(ProjectConfig()) - } +init { + addPreference(InterfaceConfig()) + addPreference(ProjectConfig()) +} } @Parcelize class InterfaceConfig( - override val key: String = "idepref_general_interface", - override val title: Int = string.title_interface, - override val children: List = mutableListOf(), +override val key: String = "idepref_general_interface", +override val title: Int = string.title_interface, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(UiMode()) - addPreference(LocaleSelector()) - } +init { + addPreference(UiMode()) + addPreference(LocaleSelector()) +} } @Parcelize class ProjectConfig( - override val key: String = "idepref_general_project", - override val title: Int = R.string.idepref_general_projectConfig, - override val children: List = mutableListOf(), +override val key: String = "idepref_general_project", +override val title: Int = R.string.idepref_general_projectConfig, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(OpenLastProject()) - addPreference(ConfirmProjectOpen()) - } +init { + addPreference(OpenLastProject()) + addPreference(ConfirmProjectOpen()) +} } @Parcelize class UiMode( - override val key: String = GeneralPreferences.UI_MODE, - override val title: Int = R.string.idepref_general_uiMode, - override val summary: Int? = R.string.idepref_general_uiMode_summary, - override val icon: Int? = R.drawable.ic_ui_mode +override val key: String = GeneralPreferences.UI_MODE, +override val title: Int = R.string.idepref_general_uiMode, +override val summary: Int? = R.string.idepref_general_uiMode_summary, +override val icon: Int? = R.drawable.ic_ui_mode ) : SingleChoicePreference() { - @IgnoredOnParcel - override val tooltipTag: String = PREFS_GENERAL +@IgnoredOnParcel +override val tooltipTag: String = PREFS_GENERAL_UIMODE - override fun getEntries(preference: Preference): Array { - val context = preference.context - val currentUiMode = GeneralPreferences.uiMode +override fun getEntries(preference: Preference): Array { + val context = preference.context + val currentUiMode = GeneralPreferences.uiMode - return Array(3) { index -> - val (label, mode) = when (index) { - 0 -> context.getString(R.string.uiMode_light) to AppCompatDelegate.MODE_NIGHT_NO - 1 -> context.getString(R.string.uiMode_dark) to AppCompatDelegate.MODE_NIGHT_YES - 2 -> context.getString(R.string.uiMode_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - else -> throw IllegalStateException("Invalid index") - } + return Array(3) { index -> + val (label, mode) = when (index) { + 0 -> context.getString(R.string.uiMode_light) to AppCompatDelegate.MODE_NIGHT_NO + 1 -> context.getString(R.string.uiMode_dark) to AppCompatDelegate.MODE_NIGHT_YES + 2 -> context.getString(R.string.uiMode_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + else -> throw IllegalStateException("Invalid index") + } - PreferenceChoices.Entry(label, currentUiMode == mode, mode) - } - } + PreferenceChoices.Entry(label, currentUiMode == mode, mode) + } +} - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - GeneralPreferences.uiMode = (entry?.data as? Int?) ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + GeneralPreferences.uiMode = (entry?.data as? Int?) ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM +} } @Parcelize class LocaleSelector( - override val key: String = GeneralPreferences.SELECTED_LOCALE, - override val title: Int = R.string.idepref_general_localeSelector_title, - override val summary: Int? = R.string.idepref_general_localeSelector_summary, - override val icon: Int? = R.drawable.ic_translate +override val key: String = GeneralPreferences.SELECTED_LOCALE, +override val title: Int = R.string.idepref_general_localeSelector_title, +override val summary: Int? = R.string.idepref_general_localeSelector_summary, +override val icon: Int? = R.drawable.ic_translate ) : SingleChoicePreference() { - @IgnoredOnParcel - override val tooltipTag: String = PREFS_GENERAL - - override fun getEntries(preference: Preference): Array { - val context = preference.context - val currentLocale = GeneralPreferences.selectedLocale - val supportedLocales = LocaleProvider.SUPPORTED_LOCALES.keys.toList() - return Array(supportedLocales.size + 1) { index -> - if (index == 0) { - PreferenceChoices.Entry( - label = ContextCompat.getString(context, R.string.locale_system_default), - _isChecked = GeneralPreferences.selectedLocale == null, - data = 0 - ) - } else { - val localeKey = supportedLocales[index - 1] - val locale = LocaleProvider.getLocale(localeKey)!! - PreferenceChoices.Entry( - label = locale.getDisplayName(locale), - _isChecked = currentLocale == localeKey, - data = localeKey - ) - } - } - } - - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - GeneralPreferences.selectedLocale = entry?.data?.let { localeKey -> - if (localeKey is Int) null else localeKey as String - } - } +@IgnoredOnParcel +override val tooltipTag: String = PREFS_GENERAL_LANGUAGE + +override fun getEntries(preference: Preference): Array { + val context = preference.context + val currentLocale = GeneralPreferences.selectedLocale + val supportedLocales = LocaleProvider.SUPPORTED_LOCALES.keys.toList() + return Array(supportedLocales.size + 1) { index -> + if (index == 0) { + PreferenceChoices.Entry( + label = ContextCompat.getString(context, R.string.locale_system_default), + _isChecked = GeneralPreferences.selectedLocale == null, + data = 0 + ) + } else { + val localeKey = supportedLocales[index - 1] + val locale = LocaleProvider.getLocale(localeKey)!! + PreferenceChoices.Entry( + label = locale.getDisplayName(locale), + _isChecked = currentLocale == localeKey, + data = localeKey + ) + } + } +} + +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + GeneralPreferences.selectedLocale = entry?.data?.let { localeKey -> + if (localeKey is Int) null else localeKey as String + } +} } @Parcelize class OpenLastProject( - override val key: String = GeneralPreferences.OPEN_PROJECTS, - override val title: Int = string.title_open_projects, - override val summary: Int? = string.msg_open_projects, - override val icon: Int? = drawable.ic_open_project +override val key: String = GeneralPreferences.OPEN_PROJECTS, +override val title: Int = string.title_open_projects, +override val summary: Int? = string.msg_open_projects, +override val icon: Int? = drawable.ic_open_project, +override val tooltipTag: String = PREFS_GENERAL_OPENLAST, ) : SwitchPreference() { - override fun onCreatePreference(context: Context): Preference { - val pref = super.onCreatePreference(context) as androidx.preference.SwitchPreference - pref.isChecked = GeneralPreferences.autoOpenProjects - return pref - } +override fun onCreatePreference(context: Context): Preference { + val pref = super.onCreatePreference(context) as androidx.preference.SwitchPreference + pref.isChecked = GeneralPreferences.autoOpenProjects + return pref +} - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - GeneralPreferences.autoOpenProjects = newValue as Boolean? - ?: GeneralPreferences.autoOpenProjects - return true - } +override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + GeneralPreferences.autoOpenProjects = newValue as Boolean? + ?: GeneralPreferences.autoOpenProjects + return true +} } @Parcelize class ConfirmProjectOpen( - override val key: String = GeneralPreferences.CONFIRM_PROJECT_OPEN, - override val title: Int = string.title_confirm_project_open, - override val summary: Int? = string.msg_confirm_project_open, - override val icon: Int? = drawable.ic_open_project +override val key: String = GeneralPreferences.CONFIRM_PROJECT_OPEN, +override val title: Int = string.title_confirm_project_open, +override val summary: Int? = string.msg_confirm_project_open, +override val icon: Int? = drawable.ic_open_project, +override val tooltipTag: String = PREFS_GENERAL_CONFIRMOPEN, ) : SwitchPreference() { - override fun onCreatePreference(context: Context): Preference { - val pref = super.onCreatePreference(context) as androidx.preference.SwitchPreference - pref.isChecked = GeneralPreferences.confirmProjectOpen - return pref - } +override fun onCreatePreference(context: Context): Preference { + val pref = super.onCreatePreference(context) as androidx.preference.SwitchPreference + pref.isChecked = GeneralPreferences.confirmProjectOpen + return pref +} - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - GeneralPreferences.confirmProjectOpen = newValue as Boolean? - ?: GeneralPreferences.confirmProjectOpen - return true - } +override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + GeneralPreferences.confirmProjectOpen = newValue as Boolean? + ?: GeneralPreferences.confirmProjectOpen + return true +} } @Parcelize class UseSytemShell( - override val key: String = GeneralPreferences.TERMINAL_USE_SYSTEM_SHELL, - override val title: Int = string.title_default_shell, - override val summary: Int? = string.msg_default_shell, - override val icon: Int? = drawable.ic_bash_commands +override val key: String = GeneralPreferences.TERMINAL_USE_SYSTEM_SHELL, +override val title: Int = string.title_default_shell, +override val summary: Int? = string.msg_default_shell, +override val icon: Int? = drawable.ic_bash_commands ) : SwitchPreference() { - override fun onCreatePreference(context: Context): Preference { - val pref = super.onCreatePreference(context) as androidx.preference.SwitchPreference - pref.isChecked = GeneralPreferences.useSystemShell - return pref - } +override fun onCreatePreference(context: Context): Preference { + val pref = super.onCreatePreference(context) as androidx.preference.SwitchPreference + pref.isChecked = GeneralPreferences.useSystemShell + return pref +} - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - GeneralPreferences.useSystemShell = newValue as Boolean? ?: GeneralPreferences.useSystemShell - return true - } +override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + GeneralPreferences.useSystemShell = newValue as Boolean? ?: GeneralPreferences.useSystemShell + return true +} } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt index 91fe9cc959..44197b1985 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt @@ -18,6 +18,7 @@ package com.itsaky.androidide.preferences import com.itsaky.androidide.R +import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.preferences.internal.JavaPreferences import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string @@ -25,22 +26,23 @@ import kotlinx.parcelize.Parcelize @Parcelize internal class JavaCodeConfigurations( - override val key: String = "idepref_editor_java", - override val title: Int = string.idepref_editor_category_java, - override val children: List = mutableListOf(), +override val key: String = "idepref_editor_java", +override val title: Int = string.idepref_editor_category_java, +override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(GoogleCodeStyle()) - } +init { + addPreference(GoogleCodeStyle()) +} } /** @author Akash Yadav */ @Parcelize private class GoogleCodeStyle( - override val key: String = JavaPreferences.GOOGLE_CODE_STYLE, - override val title: Int = string.idepref_java_useGoogleStyle_title, - override val summary: Int? = string.idepref_java_useGoogleStyle_summary, - override val icon: Int? = drawable.ic_format_code, +override val key: String = JavaPreferences.GOOGLE_CODE_STYLE, +override val title: Int = string.idepref_java_useGoogleStyle_title, +override val summary: Int? = string.idepref_java_useGoogleStyle_summary, +override val icon: Int? = drawable.ic_format_code, +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_GOOGLESTYLE, ) : SwitchPreference(getValue = JavaPreferences::googleCodeStyle::get, - setValue = JavaPreferences::googleCodeStyle::set) +setValue = JavaPreferences::googleCodeStyle::set) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt index 8e975a0993..f1fc6e163f 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt @@ -26,210 +26,222 @@ import org.eclipse.lemminx.dom.builder.EmptyElements @Parcelize class XMLPreferencesScreen( - override val key: String = "idepref_editor_xml", - override val title: Int = string.xml, - override val children: List = mutableListOf() +override val key: String = "idepref_editor_xml", +override val title: Int = string.xml, +override val children: List = mutableListOf() ) : IPreferenceGroup() { - init { - addPreference(XMLFormattingOptions()) - } +init { + addPreference(XMLFormattingOptions()) +} } @Parcelize private class XMLFormattingOptions( - override val key: String = "idepref_xml_formattingOptions", - override val title: Int = string.xml_formatting_options, - override val summary: Int? = string.xml_formatting_options_summary, - override val children: List = mutableListOf() +override val key: String = "idepref_xml_formattingOptions", +override val title: Int = string.xml_formatting_options, +override val summary: Int? = string.xml_formatting_options_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, ) : IPreferenceScreen() { - init { - addPreference(TrimFinalNewLines()) - addPreference(InsertFinalNewLine()) - addPreference(SplitAttributes()) - addPreference(JoinCDataLines()) - addPreference(JoinCommentLines()) - addPreference(JoinContentLines()) - addPreference(SpaceBeforeEmptyCloseTag()) - addPreference(PreserveEmptyContent()) - addPreference(PreserveAttributeLineBreaks()) - addPreference(ClosingBracketNewLine()) - addPreference(TrimTrailingWhitespace()) - addPreference(MaxLineWidth()) - addPreference(PreservedNewLines()) - addPreference(SplitAttributesIndentSize()) - addPreference(EmptyElementsBehavior()) - } +init { + addPreference(TrimFinalNewLines()) + addPreference(InsertFinalNewLine()) + addPreference(SplitAttributes()) + addPreference(JoinCDataLines()) + addPreference(JoinCommentLines()) + addPreference(JoinContentLines()) + addPreference(SpaceBeforeEmptyCloseTag()) + addPreference(PreserveEmptyContent()) + addPreference(PreserveAttributeLineBreaks()) + addPreference(ClosingBracketNewLine()) + addPreference(TrimTrailingWhitespace()) + addPreference(MaxLineWidth()) + addPreference(PreservedNewLines()) + addPreference(SplitAttributesIndentSize()) + addPreference(EmptyElementsBehavior()) +} } @Parcelize private class TrimFinalNewLines( - override val key: String = XmlPreferences.TRIM_FINAL_NEW_LINE, - override val title: Int = string.idepref_xml_trimFinalNewLine_title, - override val summary: Int? = string.idepref_xml_trimFinalNewLine_summary +override val key: String = XmlPreferences.TRIM_FINAL_NEW_LINE, +override val title: Int = string.idepref_xml_trimFinalNewLine_title, +override val summary: Int? = string.idepref_xml_trimFinalNewLine_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_TRIMFINALNEWLINE, ) : SwitchPreference(setValue = XmlPreferences::trimFinalNewLine::set, - getValue = XmlPreferences::trimFinalNewLine::get) +getValue = XmlPreferences::trimFinalNewLine::get) @Parcelize private class InsertFinalNewLine( - override val key: String = XmlPreferences.INSERT_FINAL_NEW_LINE, - override val title: Int = string.idepref_xml_insertFinalNewLine_title, - override val summary: Int? = string.idepref_xml_insertFinalNewLine_summary +override val key: String = XmlPreferences.INSERT_FINAL_NEW_LINE, +override val title: Int = string.idepref_xml_insertFinalNewLine_title, +override val summary: Int? = string.idepref_xml_insertFinalNewLine_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_INSERTFINALNEWLINE, ) : SwitchPreference(setValue = XmlPreferences::insertFinalNewLine::set, - getValue = XmlPreferences::insertFinalNewLine::get) +getValue = XmlPreferences::insertFinalNewLine::get) @Parcelize private class SplitAttributes( - override val key: String = XmlPreferences.SPLIT_ATTRIBUTES, - override val title: Int = string.idepref_xml_splitAttributes_title, - override val summary: Int? = string.idepref_xml_splitAttributes_summary +override val key: String = XmlPreferences.SPLIT_ATTRIBUTES, +override val title: Int = string.idepref_xml_splitAttributes_title, +override val summary: Int? = string.idepref_xml_splitAttributes_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_SPLITATTRIBUTES, ) : SwitchPreference(setValue = XmlPreferences::splitAttributes::set, - getValue = XmlPreferences::splitAttributes::get) +getValue = XmlPreferences::splitAttributes::get) @Parcelize private class JoinCDataLines( - override val key: String = XmlPreferences.JOIN_CDATA_LINES, - override val title: Int = string.idepref_xml_joinCDataLines_title, - override val summary: Int? = string.idepref_xml_joinCDataLines_summary +override val key: String = XmlPreferences.JOIN_CDATA_LINES, +override val title: Int = string.idepref_xml_joinCDataLines_title, +override val summary: Int? = string.idepref_xml_joinCDataLines_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_JOINCDATALINES, ) : SwitchPreference(setValue = XmlPreferences::joinCDataLines::set, - getValue = XmlPreferences::joinCDataLines::get) +getValue = XmlPreferences::joinCDataLines::get) @Parcelize private class JoinCommentLines( - override val key: String = XmlPreferences.JOIN_COMMENT_LINES, - override val title: Int = string.idepref_xml_joinComment_title, - override val summary: Int? = string.idepref_xml_joinComment_summary +override val key: String = XmlPreferences.JOIN_COMMENT_LINES, +override val title: Int = string.idepref_xml_joinComment_title, +override val summary: Int? = string.idepref_xml_joinComment_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_JOINCOMMENTLINES, ) : SwitchPreference(setValue = XmlPreferences::joinCommentLines::set, - getValue = XmlPreferences::joinCommentLines::get) +getValue = XmlPreferences::joinCommentLines::get) @Parcelize private class JoinContentLines( - override val key: String = XmlPreferences.JOIN_CONTENT_LINES, - override val title: Int = string.idepref_xml_joinContent_title, - override val summary: Int? = string.idepref_xml_joinContent_summary +override val key: String = XmlPreferences.JOIN_CONTENT_LINES, +override val title: Int = string.idepref_xml_joinContent_title, +override val summary: Int? = string.idepref_xml_joinContent_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_JOINCONTENTLINES, ) : SwitchPreference(setValue = XmlPreferences::joinContentLines::set, - getValue = XmlPreferences::joinContentLines::get) +getValue = XmlPreferences::joinContentLines::get) @Parcelize private class SpaceBeforeEmptyCloseTag( - override val key: String = XmlPreferences.SPACE_BEFORE_EMPTY_CLOSE_TAG, - override val title: Int = string.idepref_xml_spaceBeforeEmptyClose_title, - override val summary: Int? = string.idepref_xml_spaceBeforeEmptyClose_summary +override val key: String = XmlPreferences.SPACE_BEFORE_EMPTY_CLOSE_TAG, +override val title: Int = string.idepref_xml_spaceBeforeEmptyClose_title, +override val summary: Int? = string.idepref_xml_spaceBeforeEmptyClose_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_SPACEBEFORECLOSE, ) : - SwitchPreference( - setValue = XmlPreferences::spaceBeforeEmptyCloseTag::set, - getValue = XmlPreferences::spaceBeforeEmptyCloseTag::get - ) +SwitchPreference( + setValue = XmlPreferences::spaceBeforeEmptyCloseTag::set, + getValue = XmlPreferences::spaceBeforeEmptyCloseTag::get +) @Parcelize private class PreserveEmptyContent( - override val key: String = XmlPreferences.PRESERVE_EMPTY_CONTENT, - override val title: Int = string.idepref_xml_preserveEmptyContent_title, - override val summary: Int? = string.idepref_xml_preserveEmptyContent_summary +override val key: String = XmlPreferences.PRESERVE_EMPTY_CONTENT, +override val title: Int = string.idepref_xml_preserveEmptyContent_title, +override val summary: Int? = string.idepref_xml_preserveEmptyContent_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_PRESERVEEMPTYCONTENT, ) : - SwitchPreference(setValue = XmlPreferences::preserveEmptyContent::set, - getValue = XmlPreferences::preserveEmptyContent::get) +SwitchPreference(setValue = XmlPreferences::preserveEmptyContent::set, + getValue = XmlPreferences::preserveEmptyContent::get) @Parcelize private class PreserveAttributeLineBreaks( - override val key: String = XmlPreferences.PRESERVE_ATTRIBUTE_LINE_BREAKS, - override val title: Int = string.idepref_xml_preserveAttrLineBreaks_title, - override val summary: Int? = string.idepref_xml_preserveAttrLineBreaks_summary +override val key: String = XmlPreferences.PRESERVE_ATTRIBUTE_LINE_BREAKS, +override val title: Int = string.idepref_xml_preserveAttrLineBreaks_title, +override val summary: Int? = string.idepref_xml_preserveAttrLineBreaks_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_PRESERVEATTRIBUTES, ) : - SwitchPreference( - setValue = XmlPreferences::preserveAttributeLineBreaks::set, - getValue = XmlPreferences::preserveAttributeLineBreaks::get - ) +SwitchPreference( + setValue = XmlPreferences::preserveAttributeLineBreaks::set, + getValue = XmlPreferences::preserveAttributeLineBreaks::get +) @Parcelize private class ClosingBracketNewLine( - override val key: String = XmlPreferences.CLOSING_BRACKET_NEW_LINE, - override val title: Int = string.idepref_xml_closingBrackNewLine_title, - override val summary: Int? = string.idepref_xml_closingBrackNewLine_summary +override val key: String = XmlPreferences.CLOSING_BRACKET_NEW_LINE, +override val title: Int = string.idepref_xml_closingBrackNewLine_title, +override val summary: Int? = string.idepref_xml_closingBrackNewLine_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_CLOSEBRACKET, ) : - SwitchPreference( - setValue = XmlPreferences::closingBracketNewLine::set, - getValue = XmlPreferences::closingBracketNewLine::get - ) +SwitchPreference( + setValue = XmlPreferences::closingBracketNewLine::set, + getValue = XmlPreferences::closingBracketNewLine::get +) @Parcelize private class TrimTrailingWhitespace( - override val key: String = XmlPreferences.TRIM_TRAILING_WHITESPACE, - override val title: Int = string.idepref_xml_trimTrailingWs_title, - override val summary: Int? = string.idepref_xml_trimTrailingWs_summary +override val key: String = XmlPreferences.TRIM_TRAILING_WHITESPACE, +override val title: Int = string.idepref_xml_trimTrailingWs_title, +override val summary: Int? = string.idepref_xml_trimTrailingWs_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_TRIMWHITESPACE, ) : - SwitchPreference( - setValue = XmlPreferences::trimTrailingWhitespace::set, - getValue = XmlPreferences::trimTrailingWhitespace::get - ) +SwitchPreference( + setValue = XmlPreferences::trimTrailingWhitespace::set, + getValue = XmlPreferences::trimTrailingWhitespace::get +) @Parcelize private class MaxLineWidth( - override val key: String = XmlPreferences.MAX_LINE_WIDTH, - override val title: Int = string.idepref_maxLineWidth_title, - override val summary: Int? = string.idepref_maxLineWidth_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.MAX_LINE_WIDTH, +override val title: Int = string.idepref_maxLineWidth_title, +override val summary: Int? = string.idepref_maxLineWidth_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_MAXLINEWIDTH, ) : - NumberInputEditTextPreference( - hint = string.idepref_maxLineWidth_title, - setValue = XmlPreferences::maxLineWidth::set, - getValue = XmlPreferences::maxLineWidth::get - ) +NumberInputEditTextPreference( + hint = string.idepref_maxLineWidth_title, + setValue = XmlPreferences::maxLineWidth::set, + getValue = XmlPreferences::maxLineWidth::get +) @Parcelize private class PreservedNewLines( - override val key: String = XmlPreferences.PRESERVED_NEW_LINES, - override val title: Int = string.idepref_preservedNewLines_title, - override val summary: Int? = string.idepref_preservedNewLines_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.PRESERVED_NEW_LINES, +override val title: Int = string.idepref_preservedNewLines_title, +override val summary: Int? = string.idepref_preservedNewLines_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_PRESERVENEWLINES, ) : - NumberInputEditTextPreference( - hint = string.idepref_preservedNewLines_title, - setValue = XmlPreferences::preservedNewLines::set, - getValue = XmlPreferences::preservedNewLines::get - ) +NumberInputEditTextPreference( + hint = string.idepref_preservedNewLines_title, + setValue = XmlPreferences::preservedNewLines::set, + getValue = XmlPreferences::preservedNewLines::get +) @Parcelize private class SplitAttributesIndentSize( - override val key: String = XmlPreferences.SPLIT_ATTRIBUTES_INDENT_SIZE, - override val title: Int = string.idepref_splitAttrIndentSize_title, - override val summary: Int? = string.idepref_splitAttrIndentSize_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.SPLIT_ATTRIBUTES_INDENT_SIZE, +override val title: Int = string.idepref_splitAttrIndentSize_title, +override val summary: Int? = string.idepref_splitAttrIndentSize_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_SPLITATTRIBINDENT, ) : - NumberInputEditTextPreference( - hint = string.idepref_splitAttrIndentSize_title, - setValue = XmlPreferences::splitAttributesIndentSize::set, - getValue = XmlPreferences::splitAttributesIndentSize::get - ) +NumberInputEditTextPreference( + hint = string.idepref_splitAttrIndentSize_title, + setValue = XmlPreferences::splitAttributesIndentSize::set, + getValue = XmlPreferences::splitAttributesIndentSize::get +) @Parcelize private class EmptyElementsBehavior( - override val key: String = XmlPreferences.EMPTY_ELEMENTS_BEHAVIOR, - override val title: Int = string.idepref_emptyElements_title, - override val summary: Int? = string.idepref_emptyElements_summary, - override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, +override val key: String = XmlPreferences.EMPTY_ELEMENTS_BEHAVIOR, +override val title: Int = string.idepref_emptyElements_title, +override val summary: Int? = string.idepref_emptyElements_summary, +override val tooltipTag: String = TooltipTag.PREFS_XML_EMPTYELEMENTS, ) : SingleChoicePreference() { - override fun getEntries(preference: Preference): Array { - val entries = EmptyElements.entries - val currentBehavior = EmptyElements.valueOf(XmlPreferences.emptyElementsBehavior) - - return Array(entries.size) { index -> - PreferenceChoices.Entry( - label = entries[index].toString(), - _isChecked = currentBehavior == entries[index], - data = entries[index] - ) - } - } - - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - XmlPreferences.emptyElementsBehavior = (entry?.data as? EmptyElements?)?.toString() - ?: "Collapse" - } +override fun getEntries(preference: Preference): Array { + val entries = EmptyElements.entries + val currentBehavior = EmptyElements.valueOf(XmlPreferences.emptyElementsBehavior) + + return Array(entries.size) { index -> + PreferenceChoices.Entry( + label = entries[index].toString(), + _isChecked = currentBehavior == entries[index], + data = entries[index] + ) + } +} + +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + XmlPreferences.emptyElementsBehavior = (entry?.data as? EmptyElements?)?.toString() + ?: "Collapse" +} } From 369b1f3d90b3897e613a0547b990c51e1f256de5 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:16:36 -0700 Subject: [PATCH 04/23] ADFA-5088: Tag Build & Run items and each Gradle flag individually Tag the Build & Run screen and its items, and use the per-entry tooltip tag hook added earlier so each of the 7 "Additional Gradle flags" checkboxes (--info, --stacktrace, etc.) gets its own tag instead of sharing the dialog's tag. Co-Authored-By: Claude Sonnet 5 --- .../preferences/buildAndRunPrefExts.kt | 109 +++++++++++------- 1 file changed, 66 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt index f3e1de6d40..5699a47e1f 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt @@ -18,7 +18,16 @@ package com.itsaky.androidide.preferences import com.itsaky.androidide.R -import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GRADLE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_AUTOLAUNCH +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_BUILDCACHE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_DEBUG +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_INFO +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_OFFLINE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_SCAN +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_STACKTRACE +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILDRUN_FLAGS_WARNINGMODEALL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_BUILD_RUN import com.itsaky.androidide.preferences.internal.BuildPreferences.GRADLE_COMMANDS import com.itsaky.androidide.preferences.internal.BuildPreferences.LAUNCH_APP_AFTER_INSTALL import com.itsaky.androidide.preferences.internal.BuildPreferences.isBuildCacheEnabled @@ -37,72 +46,86 @@ import kotlin.reflect.KMutableProperty0 @Parcelize class BuildAndRunPreferences( - override val key: String = "idepref_build_n_run", - override val title: Int = string.idepref_build_title, - override val summary: Int? = string.idepref_buildnrun_summary, - override val children: List = mutableListOf(), + override val key: String = "idepref_build_n_run", + override val title: Int = string.idepref_build_title, + override val summary: Int? = string.idepref_buildnrun_summary, + override val children: List = mutableListOf(), + override val tooltipTag: String = PREFS_BUILD_RUN, ) : IPreferenceScreen() { - init { - addPreference(GradleOptions()) - addPreference(RunOptions()) - } + init { + addPreference(GradleOptions()) + addPreference(RunOptions()) + } } @Parcelize private class GradleOptions( - override val key: String = "idepref_build_gradle", - override val title: Int = string.gradle, - override val children: List = mutableListOf(), + override val key: String = "idepref_build_gradle", + override val title: Int = string.gradle, + override val children: List = mutableListOf(), ) : IPreferenceGroup() { - init { - addPreference(GradleCommands()) - } +init { + addPreference(GradleCommands()) +} } @Parcelize private class GradleCommands( - override val key: String = GRADLE_COMMANDS, - override val title: Int = string.idepref_build_customgradlecommands_title, - override val summary: Int? = string.idepref_build_customgradlecommands_summary, - override val icon: Int? = drawable.ic_bash_commands, + override val key: String = GRADLE_COMMANDS, + override val title: Int = string.idepref_build_customgradlecommands_title, + override val summary: Int? = string.idepref_build_customgradlecommands_summary, + override val icon: Int? = drawable.ic_bash_commands, ) : PropertyBasedMultiChoicePreference() { - @IgnoredOnParcel - override val tooltipTag: String = PREFS_GRADLE + @IgnoredOnParcel + override val tooltipTag: String = PREFS_BUILDRUN_FLAGS + + override fun getProperties(): Map> { + return linkedMapOf( + "--stacktrace" to ::isStacktraceEnabled, + "--info" to ::isInfoEnabled, + "--debug" to ::isDebugEnabled, + "--scan" to ::isScanEnabled, + "--warning-mode all" to ::isWarningModeAllEnabled, + "--build-cache" to ::isBuildCacheEnabled, + "--offline" to ::isOfflineEnabled, + ) + } - override fun getProperties(): Map> { - return linkedMapOf( - "--stacktrace" to ::isStacktraceEnabled, - "--info" to ::isInfoEnabled, - "--debug" to ::isDebugEnabled, - "--scan" to ::isScanEnabled, - "--warning-mode all" to ::isWarningModeAllEnabled, - "--build-cache" to ::isBuildCacheEnabled, - "--offline" to ::isOfflineEnabled, - ) - } + override fun getEntryTooltipTags(): Map { + return mapOf( + "--stacktrace" to PREFS_BUILDRUN_FLAGS_STACKTRACE, + "--info" to PREFS_BUILDRUN_FLAGS_INFO, + "--debug" to PREFS_BUILDRUN_FLAGS_DEBUG, + "--scan" to PREFS_BUILDRUN_FLAGS_SCAN, + "--warning-mode all" to PREFS_BUILDRUN_FLAGS_WARNINGMODEALL, + "--build-cache" to PREFS_BUILDRUN_FLAGS_BUILDCACHE, + "--offline" to PREFS_BUILDRUN_FLAGS_OFFLINE, + ) + } } @Parcelize private class RunOptions( - override val key: String = "ide.build.runOptions", - override val title: Int = R.string.title_run_options, - override val children: List = mutableListOf() + override val key: String = "ide.build.runOptions", + override val title: Int = R.string.title_run_options, + override val children: List = mutableListOf() ) : IPreferenceGroup() { - init { - addPreference(LaunchAppAfterInstall()) - } + init { + addPreference(LaunchAppAfterInstall()) + } } @Parcelize private class LaunchAppAfterInstall( - override val key: String = LAUNCH_APP_AFTER_INSTALL, - override val title: Int = R.string.idepref_launchAppAfterInstall_title, - override val summary: Int? = R.string.idepref_launchAppAfterInstall_summary, - override val icon: Int? = drawable.ic_open_external + override val key: String = LAUNCH_APP_AFTER_INSTALL, + override val title: Int = R.string.idepref_launchAppAfterInstall_title, + override val summary: Int? = R.string.idepref_launchAppAfterInstall_summary, + override val icon: Int? = drawable.ic_open_external, + override val tooltipTag: String = PREFS_BUILDRUN_AUTOLAUNCH, ) : - SwitchPreference(setValue = ::launchAppAfterInstall::set, getValue = ::launchAppAfterInstall::get) +SwitchPreference(setValue = ::launchAppAfterInstall::set, getValue = ::launchAppAfterInstall::get) From 2a0be9a1785debaa514fa735fa6d66affb503641 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:16:54 -0700 Subject: [PATCH 05/23] ADFA-5088: Tag Terminal, Git, Developer Options, Plugin Manager, About Give every item on the Terminal screen, the Git screen, the Developer Options screen, and the Plugin Manager and About entry rows its own tooltip tag. Co-Authored-By: Claude Sonnet 5 --- .../androidide/preferences/aboutPrefExts.kt | 18 +- .../preferences/developerOptionsPrefExts.kt | 61 ++-- .../androidide/preferences/gitPrefExts.kt | 148 +++++---- .../androidide/preferences/pluginPrefExts.kt | 2 + .../androidide/preferences/termuxPrefsExt.kt | 303 +++++++++--------- 5 files changed, 281 insertions(+), 251 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt index 049531ca0f..5ccdd1a7ef 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/aboutPrefExts.kt @@ -19,16 +19,18 @@ package com.itsaky.androidide.preferences import android.content.Intent import com.itsaky.androidide.activities.AboutActivity +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_ABOUT import com.itsaky.androidide.resources.R private const val KEY_ABOUT = "idepref_about" val about = - SimpleClickablePreference( - key = KEY_ABOUT, - title = R.string.idepref_about_title, - summary = R.string.idepref_about_summary - ) { - it.context.startActivity(Intent(it.context, AboutActivity::class.java)) - true - } \ No newline at end of file +SimpleClickablePreference( + key = KEY_ABOUT, + title = R.string.idepref_about_title, + summary = R.string.idepref_about_summary, + tooltipTag = PREFS_ABOUT, +) { + it.context.startActivity(Intent(it.context, AboutActivity::class.java)) + true +} diff --git a/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt index 0f37c26dc3..bae79edf12 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt @@ -18,46 +18,53 @@ package com.itsaky.androidide.preferences import com.itsaky.androidide.R +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVOPTIONS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVOPTIONS_DUMPLOGS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_DEVOPTIONS_LOGSENDER import com.itsaky.androidide.preferences.internal.DevOpsPreferences import kotlinx.parcelize.Parcelize @Parcelize internal class DeveloperOptionsScreen( - override val key: String = DevOpsPreferences.KEY_DEVOPTS, - override val title: Int = R.string.title_developer_options, - override val summary: Int? = R.string.idepref_devOptions_summary, - override val children: List = mutableListOf()) : IPreferenceScreen() { - - init { - addPreference(DumpLogsPreference()) - addPreference(EnableLogSenderPreference()) - } +override val key: String = DevOpsPreferences.KEY_DEVOPTS, +override val title: Int = R.string.title_developer_options, +override val summary: Int? = R.string.idepref_devOptions_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_DEVOPTIONS, +) : IPreferenceScreen() { + +init { + addPreference(DumpLogsPreference()) + addPreference(EnableLogSenderPreference()) +} } @Parcelize internal class DebuggingPreferences( - override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING, - override val title: Int = R.string.idepref_group_debugging, - override val children: List = mutableListOf()) : IPreferenceGroup() { - - init { - addPreference(DumpLogsPreference()) - addPreference(EnableLogSenderPreference()) - } +override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING, +override val title: Int = R.string.idepref_group_debugging, +override val children: List = mutableListOf()) : IPreferenceGroup() { + +init { + addPreference(DumpLogsPreference()) + addPreference(EnableLogSenderPreference()) +} } @Parcelize internal class DumpLogsPreference( - override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_DUMPLOGS, - override val title: Int = R.string.idepref_devOptions_dumpLogs_title, - override val summary: Int? = R.string.idepref_devOptions_dumpLogs_summary) : - SwitchPreference(setValue = DevOpsPreferences::dumpLogs::set, - getValue = DevOpsPreferences::dumpLogs::get) +override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_DUMPLOGS, +override val title: Int = R.string.idepref_devOptions_dumpLogs_title, +override val summary: Int? = R.string.idepref_devOptions_dumpLogs_summary, +override val tooltipTag: String = PREFS_DEVOPTIONS_DUMPLOGS) : +SwitchPreference(setValue = DevOpsPreferences::dumpLogs::set, + getValue = DevOpsPreferences::dumpLogs::get) @Parcelize internal class EnableLogSenderPreference( - override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_ENABLE_LOGSENDER, - override val title: Int = R.string.idepref_devOptions_enableLogsender_title, - override val summary: Int? = R.string.idepref_devOptions_enableLogsender_summary) : - SwitchPreference(setValue = DevOpsPreferences::logsenderEnabled::set, - getValue = DevOpsPreferences::logsenderEnabled::get) \ No newline at end of file +override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_ENABLE_LOGSENDER, +override val title: Int = R.string.idepref_devOptions_enableLogsender_title, +override val summary: Int? = R.string.idepref_devOptions_enableLogsender_summary, +override val tooltipTag: String = PREFS_DEVOPTIONS_LOGSENDER) : +SwitchPreference(setValue = DevOpsPreferences::logsenderEnabled::set, + getValue = DevOpsPreferences::logsenderEnabled::get) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt index dc8b50d9ee..468ef55af2 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt @@ -2,105 +2,111 @@ package com.itsaky.androidide.preferences import androidx.preference.Preference import com.google.android.material.textfield.TextInputLayout +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GIT +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GIT_USEREMAIL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_GIT_USERNAME import com.itsaky.androidide.preferences.internal.GitPreferences import com.itsaky.androidide.R import kotlinx.parcelize.Parcelize @Parcelize class GitPreferencesScreen( - override val key: String = "idepref_git", - override val title: Int = R.string.git_title, - override val summary: Int? = R.string.idepref_git_summary, - override val children: List = mutableListOf() + override val key: String = "idepref_git", + override val title: Int = R.string.git_title, + override val summary: Int? = R.string.idepref_git_summary, + override val children: List = mutableListOf(), + override val tooltipTag: String = PREFS_GIT, ) : IPreferenceScreen() { - init { - addPreference(GitAuthorConfig()) - } + init { + addPreference(GitAuthorConfig()) + } } @Parcelize class GitAuthorConfig( - override val key: String = "idepref_git_author", - override val title: Int = R.string.idepref_git_author_title, - override val summary: Int? = R.string.idepref_git_author_summary, - override val children: List = mutableListOf() + override val key: String = "idepref_git_author", + override val title: Int = R.string.idepref_git_author_title, + override val summary: Int? = R.string.idepref_git_author_summary, + override val children: List = mutableListOf() ) : IPreferenceGroup() { - init { - addPreference(GitUserName()) - addPreference(GitUserEmail()) - } + init { + addPreference(GitUserName()) + addPreference(GitUserEmail()) + } } @Parcelize class GitUserName( - override val key: String = GitPreferences.GIT_USER_NAME, - override val title: Int = R.string.idepref_git_user_name_title, - override val summary: Int? = null, - override val icon: Int? = R.drawable.ic_account + override val key: String = GitPreferences.GIT_USER_NAME, + override val title: Int = R.string.idepref_git_user_name_title, + override val summary: Int? = null, + override val icon: Int? = R.drawable.ic_account, + override val tooltipTag: String = PREFS_GIT_USERNAME, ) : EditTextPreference() { - override fun onCreateView(context: android.content.Context): Preference { - val pref = super.onCreateView(context) - val currentName = GitPreferences.userName - if (!currentName.isNullOrBlank()) { - pref.summary = currentName - } else { - pref.summary = context.getString(R.string.idepref_git_user_name_summary) - } - return pref - } + override fun onCreateView(context: android.content.Context): Preference { + val pref = super.onCreateView(context) + val currentName = GitPreferences.userName + if (!currentName.isNullOrBlank()) { + pref.summary = currentName + } else { + pref.summary = context.getString(R.string.idepref_git_user_name_summary) + } + return pref + } - override fun onConfigureTextInput(input: TextInputLayout) { - input.editText?.setText(GitPreferences.userName) - input.hint = input.context.getString(R.string.idepref_git_user_name_title) - } + override fun onConfigureTextInput(input: TextInputLayout) { + input.editText?.setText(GitPreferences.userName) + input.hint = input.context.getString(R.string.idepref_git_user_name_title) + } - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - val name = newValue as? String - GitPreferences.userName = name - if (!name.isNullOrBlank()) { - preference.summary = name - } else { - preference.summary = preference.context.getString(R.string.idepref_git_user_name_summary) - } - return true - } + override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + val name = newValue as? String + GitPreferences.userName = name + if (!name.isNullOrBlank()) { + preference.summary = name + } else { + preference.summary = preference.context.getString(R.string.idepref_git_user_name_summary) + } + return true + } } @Parcelize class GitUserEmail( - override val key: String = GitPreferences.GIT_USER_EMAIL, - override val title: Int = R.string.idepref_git_user_email_title, - override val summary: Int? = null, - override val icon: Int? = R.drawable.ic_email + override val key: String = GitPreferences.GIT_USER_EMAIL, + override val title: Int = R.string.idepref_git_user_email_title, + override val summary: Int? = null, + override val icon: Int? = R.drawable.ic_email, + override val tooltipTag: String = PREFS_GIT_USEREMAIL, ) : EditTextPreference() { - override fun onCreateView(context: android.content.Context): Preference { - val pref = super.onCreateView(context) - val currentEmail = GitPreferences.userEmail - if (!currentEmail.isNullOrBlank()) { - pref.summary = currentEmail - } else { - pref.summary = context.getString(R.string.idepref_git_user_email_summary) - } - return pref - } + override fun onCreateView(context: android.content.Context): Preference { + val pref = super.onCreateView(context) + val currentEmail = GitPreferences.userEmail + if (!currentEmail.isNullOrBlank()) { + pref.summary = currentEmail + } else { + pref.summary = context.getString(R.string.idepref_git_user_email_summary) + } + return pref + } - override fun onConfigureTextInput(input: TextInputLayout) { - input.editText?.setText(GitPreferences.userEmail) - input.hint = input.context.getString(R.string.idepref_git_user_email_title) - } + override fun onConfigureTextInput(input: TextInputLayout) { + input.editText?.setText(GitPreferences.userEmail) + input.hint = input.context.getString(R.string.idepref_git_user_email_title) + } - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - val email = newValue as? String - GitPreferences.userEmail = email - if (!email.isNullOrBlank()) { - preference.summary = email - } else { - preference.summary = preference.context.getString(R.string.idepref_git_user_email_summary) - } - return true - } + override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + val email = newValue as? String + GitPreferences.userEmail = email + if (!email.isNullOrBlank()) { + preference.summary = email + } else { + preference.summary = preference.context.getString(R.string.idepref_git_user_email_summary) + } + return true + } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt index ae8098d04a..c800eb70fc 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt @@ -7,6 +7,7 @@ import androidx.preference.Preference import com.itsaky.androidide.activities.PluginManagerActivity import com.itsaky.androidide.activities.PluginScreenNavigator import com.itsaky.androidide.app.IDEApplication +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_PLUGIN_MANAGER import com.itsaky.androidide.plugins.manager.core.PluginManager import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string @@ -18,6 +19,7 @@ class PluginManagerEntry( override val key: String = "idepref_plugin_manager", override val title: Int = string.plugin_manager_title, override val summary: Int? = string.plugin_manager_summary, + override val tooltipTag: String = PREFS_PLUGIN_MANAGER, ) : BasePreference() { override fun onCreatePreference(context: Context): Preference { return Preference(context) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt index 303eacca5c..db94e5397b 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt @@ -24,6 +24,12 @@ import androidx.preference.Preference import com.itsaky.androidide.R import com.itsaky.androidide.app.IDEApplication import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_CRASHREPORTS +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_KEYLOGGING +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_LOGLEVEL +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_MARGIN +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_NOHARDKEYBOARD +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_SOFTKEYBOARD import com.termux.shared.logger.Logger import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences import kotlinx.parcelize.IgnoredOnParcel @@ -42,221 +48,228 @@ private const val KEY_TERMUX_VIEW_PREFERENCES = "${KEY_TERMUX_PREFERENCES}.view" private const val KEY_TERMUX_VIEW_MARGIN_ADJUSTMENT_ENABLED_PREFERENCE = "${KEY_TERMUX_VIEW_PREFERENCES}.marginAdjustment" abstract class TermuxSwitchPreference( - @StringRes private val summaryOn: Int, - @StringRes private val summaryOff: Int, - property: KMutableProperty0 +@StringRes private val summaryOn: Int, +@StringRes private val summaryOff: Int, +property: KMutableProperty0, +override val tooltipTag: String = "", ) : SwitchPreference(property) { - override fun onCreatePreference(context: Context): Preference { - return super.onCreatePreference(context).also { preference -> - updateSummary(preference) - } - } +override fun onCreatePreference(context: Context): Preference { + return super.onCreatePreference(context).also { preference -> + updateSummary(preference) + } +} - override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { - return super.onPreferenceChanged(preference, newValue).also { - updateSummary(preference) - } - } +override fun onPreferenceChanged(preference: Preference, newValue: Any?): Boolean { + return super.onPreferenceChanged(preference, newValue).also { + updateSummary(preference) + } +} - private fun updateSummary(preference: Preference) { - preference.summary = (if (getValue?.invoke() == true) { - summaryOn - } else { - summaryOff - }).let { summary -> - ContextCompat.getString(preference.context, summary) - } - } +private fun updateSummary(preference: Preference) { + preference.summary = (if (getValue?.invoke() == true) { + summaryOn + } else { + summaryOff + }).let { summary -> + ContextCompat.getString(preference.context, summary) + } +} } @Parcelize class TermuxPreferences( - override val key: String = KEY_TERMUX_PREFERENCES, - override val title: Int = R.string.termux_preferences_title, - override val summary: Int? = R.string.termux_preferences_summary, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_PREFERENCES, +override val title: Int = R.string.termux_preferences_title, +override val summary: Int? = R.string.termux_preferences_summary, +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceScreen() { - init { - addPreference(TermuxDebuggingPreferences()) - addPreference(TermuxKeyboardPreferences()) - addPreference(TermuxViewPreferences()) - } +init { + addPreference(TermuxDebuggingPreferences()) + addPreference(TermuxKeyboardPreferences()) + addPreference(TermuxViewPreferences()) +} } @Parcelize class TermuxDebuggingPreferences( - override val key: String = KEY_TERMUX_DEBUGGING_PREFERENCES, - override val title: Int = R.string.termux_debugging_preferences_title, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_DEBUGGING_PREFERENCES, +override val title: Int = R.string.termux_debugging_preferences_title, +override val children: List = mutableListOf() ) : IPreferenceGroup() { - init { - addPreference(TermuxDebuggingLogLevelPreference()) - addPreference(TermuxDebuggingTerminalViewKeyLoggingPreference()) - addPreference(TermuxDebuggingCrashReportNotificationsPreference()) - } +init { + addPreference(TermuxDebuggingLogLevelPreference()) + addPreference(TermuxDebuggingTerminalViewKeyLoggingPreference()) + addPreference(TermuxDebuggingCrashReportNotificationsPreference()) +} } @Parcelize class TermuxDebuggingLogLevelPreference( - override val key: String = KEY_TERMUX_DEBUGGING_LOG_LEVEL_PREFERENCE, - override val title: Int = R.string.log_level_title, - override val icon: Int? = R.drawable.ic_bug +override val key: String = KEY_TERMUX_DEBUGGING_LOG_LEVEL_PREFERENCE, +override val title: Int = R.string.log_level_title, +override val icon: Int? = R.drawable.ic_bug ) : SingleChoicePreference() { - @IgnoredOnParcel - override val tooltipTag: String = PREFS_TERMUX +@IgnoredOnParcel +override val tooltipTag: String = PREFS_TERMUX_LOGLEVEL - override fun getEntries(preference: Preference): Array { - val logLevels = Logger.getLogLevelsArray() - val logLevelLabels = Logger.getLogLevelLabelsArray(preference.context, logLevels, true) - val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel - ?: Logger.DEFAULT_LOG_LEVEL - return Array(logLevels.size) { - PreferenceChoices.Entry(logLevelLabels[it], currentLogLevel == logLevels[it], - logLevels[it]) - } - } +override fun getEntries(preference: Preference): Array { + val logLevels = Logger.getLogLevelsArray() + val logLevelLabels = Logger.getLogLevelLabelsArray(preference.context, logLevels, true) + val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel + ?: Logger.DEFAULT_LOG_LEVEL + return Array(logLevels.size) { + PreferenceChoices.Entry(logLevelLabels[it], currentLogLevel == logLevels[it], + logLevels[it]) + } +} - override fun onChoiceConfirmed( - preference: Preference, - entry: PreferenceChoices.Entry?, - position: Int - ) { - val newLevel = (entry?.data as? Int?) ?: Logger.DEFAULT_LOG_LEVEL - TermuxAppSharedPreferences.build(preference.context, false) - ?.setLogLevel(preference.context, newLevel) +override fun onChoiceConfirmed( + preference: Preference, + entry: PreferenceChoices.Entry?, + position: Int +) { + val newLevel = (entry?.data as? Int?) ?: Logger.DEFAULT_LOG_LEVEL + TermuxAppSharedPreferences.build(preference.context, false) + ?.setLogLevel(preference.context, newLevel) - preference.summary = Logger.getLogLevelLabel(preference.context, newLevel, true) - } + preference.summary = Logger.getLogLevelLabel(preference.context, newLevel, true) +} - override fun onCreatePreference(context: Context): Preference { - return super.onCreatePreference(context).also { preference -> - val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel - ?: Logger.DEFAULT_LOG_LEVEL - preference.summary = Logger.getLogLevelLabel(context, currentLogLevel, true) - } - } +override fun onCreatePreference(context: Context): Preference { + return super.onCreatePreference(context).also { preference -> + val currentLogLevel = TermuxAppSharedPreferences.build(preference.context, false)?.logLevel + ?: Logger.DEFAULT_LOG_LEVEL + preference.summary = Logger.getLogLevelLabel(context, currentLogLevel, true) + } +} } private var isTerminalViewKeyLoggingEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isTerminalViewKeyLoggingEnabled - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isTerminalViewKeyLoggingEnabled = value - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isTerminalViewKeyLoggingEnabled +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isTerminalViewKeyLoggingEnabled = value +} @Parcelize class TermuxDebuggingTerminalViewKeyLoggingPreference( - override val key: String = KEY_TERMUX_DEBUGGING_TERMINAL_VIEW_KEY_LOGGING_PREFERENCE, - override val title: Int = R.string.termux_terminal_view_key_logging_enabled_title, - override val icon: Int? = R.drawable.ic_keyboard, +override val key: String = KEY_TERMUX_DEBUGGING_TERMINAL_VIEW_KEY_LOGGING_PREFERENCE, +override val title: Int = R.string.termux_terminal_view_key_logging_enabled_title, +override val icon: Int? = R.drawable.ic_keyboard, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_terminal_view_key_logging_enabled_on, - summaryOff = R.string.termux_terminal_view_key_logging_enabled_off, - property = ::isTerminalViewKeyLoggingEnabled +summaryOn = R.string.termux_terminal_view_key_logging_enabled_on, +summaryOff = R.string.termux_terminal_view_key_logging_enabled_off, +property = ::isTerminalViewKeyLoggingEnabled, +tooltipTag = PREFS_TERMUX_KEYLOGGING, ) private var isTerminalCrashReportNotificationsEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).areCrashReportNotificationsEnabled(false) - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).setCrashReportNotificationsEnabled(value) - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).areCrashReportNotificationsEnabled(false) +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).setCrashReportNotificationsEnabled(value) +} @Parcelize class TermuxDebuggingCrashReportNotificationsPreference( - override val key: String = KEY_TERMUX_DEBUGGING_CRASH_REPORT_NOTIFICATIONS_PREFERENCE, - override val title: Int = R.string.termux_crash_report_notifications_enabled_title, - override val icon: Int? = R.drawable.ic_bell, +override val key: String = KEY_TERMUX_DEBUGGING_CRASH_REPORT_NOTIFICATIONS_PREFERENCE, +override val title: Int = R.string.termux_crash_report_notifications_enabled_title, +override val icon: Int? = R.drawable.ic_bell, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_crash_report_notifications_enabled_on, - summaryOff = R.string.termux_crash_report_notifications_enabled_off, - property = ::isTerminalCrashReportNotificationsEnabled +summaryOn = R.string.termux_crash_report_notifications_enabled_on, +summaryOff = R.string.termux_crash_report_notifications_enabled_off, +property = ::isTerminalCrashReportNotificationsEnabled, +tooltipTag = PREFS_TERMUX_CRASHREPORTS, ) @Parcelize class TermuxKeyboardPreferences( - override val key: String = KEY_TERMUX_KBD_PREFERENCES, - override val title: Int = R.string.termux_keyboard_header, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_KBD_PREFERENCES, +override val title: Int = R.string.termux_keyboard_header, +override val children: List = mutableListOf() ) : IPreferenceGroup() { - init { - addPreference(TermuxKbdSoftKbdEnabledPreference()) - addPreference(TermuxKbdSoftKbdOnlyIfNoHardKbdEnabledPreference()) - } +init { + addPreference(TermuxKbdSoftKbdEnabledPreference()) + addPreference(TermuxKbdSoftKbdOnlyIfNoHardKbdEnabledPreference()) +} } private var isSoftKbdEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabled - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabled = value - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabled +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabled = value +} @Parcelize class TermuxKbdSoftKbdEnabledPreference( - override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ENABLED_PREFERENCE, - override val title: Int = R.string.termux_soft_keyboard_enabled_title, - override val icon: Int? = R.drawable.ic_keyboard_soft, +override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ENABLED_PREFERENCE, +override val title: Int = R.string.termux_soft_keyboard_enabled_title, +override val icon: Int? = R.drawable.ic_keyboard_soft, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_soft_keyboard_enabled_on, - summaryOff = R.string.termux_soft_keyboard_enabled_off, - property = ::isSoftKbdEnabled +summaryOn = R.string.termux_soft_keyboard_enabled_on, +summaryOff = R.string.termux_soft_keyboard_enabled_off, +property = ::isSoftKbdEnabled, +tooltipTag = PREFS_TERMUX_SOFTKEYBOARD, ) private var isSoftKbdOnlyIfNoHardKbdEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabledOnlyIfNoHardware - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isSoftKeyboardEnabledOnlyIfNoHardware = value - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabledOnlyIfNoHardware +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isSoftKeyboardEnabledOnlyIfNoHardware = value +} @Parcelize class TermuxKbdSoftKbdOnlyIfNoHardKbdEnabledPreference( - override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ONLY_IF_NO_HARD_KBD_PREFERENCE, - override val title: Int = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_title, - override val icon: Int? = R.drawable.ic_keyboard, +override val key: String = KEY_TERMUX_KBD_SOFT_KDB_ONLY_IF_NO_HARD_KBD_PREFERENCE, +override val title: Int = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_title, +override val icon: Int? = R.drawable.ic_keyboard, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_on, - summaryOff = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_off, - property = ::isSoftKbdOnlyIfNoHardKbdEnabled +summaryOn = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_on, +summaryOff = R.string.termux_soft_keyboard_enabled_only_if_no_hardware_off, +property = ::isSoftKbdOnlyIfNoHardKbdEnabled, +tooltipTag = PREFS_TERMUX_NOHARDKEYBOARD, ) @Parcelize class TermuxViewPreferences( - override val key: String = KEY_TERMUX_VIEW_PREFERENCES, - override val title: Int = R.string.termux_terminal_view_view_header, - override val children: List = mutableListOf() +override val key: String = KEY_TERMUX_VIEW_PREFERENCES, +override val title: Int = R.string.termux_terminal_view_view_header, +override val children: List = mutableListOf() ) : IPreferenceGroup() { - init { - addPreference(TermuxViewMarginAdjustmentEnabledPreference()) - } +init { + addPreference(TermuxViewMarginAdjustmentEnabledPreference()) +} } private var isViewMarginAdjustmentEnabled: Boolean - get() = TermuxAppSharedPreferences.build(IDEApplication.instance, - true).isTerminalMarginAdjustmentEnabled - set(value) { - TermuxAppSharedPreferences.build(IDEApplication.instance, - true).setTerminalMarginAdjustment(value) - } +get() = TermuxAppSharedPreferences.build(IDEApplication.instance, + true).isTerminalMarginAdjustmentEnabled +set(value) { + TermuxAppSharedPreferences.build(IDEApplication.instance, + true).setTerminalMarginAdjustment(value) +} @Parcelize class TermuxViewMarginAdjustmentEnabledPreference( - override val key: String = KEY_TERMUX_VIEW_MARGIN_ADJUSTMENT_ENABLED_PREFERENCE, - override val title: Int = R.string.termux_terminal_view_terminal_margin_adjustment_title, - override val icon: Int? = R.drawable.ic_space, +override val key: String = KEY_TERMUX_VIEW_MARGIN_ADJUSTMENT_ENABLED_PREFERENCE, +override val title: Int = R.string.termux_terminal_view_terminal_margin_adjustment_title, +override val icon: Int? = R.drawable.ic_space, ) : TermuxSwitchPreference( - summaryOn = R.string.termux_terminal_view_terminal_margin_adjustment_on, - summaryOff = R.string.termux_terminal_view_terminal_margin_adjustment_off, - property = ::isViewMarginAdjustmentEnabled -) \ No newline at end of file +summaryOn = R.string.termux_terminal_view_terminal_margin_adjustment_on, +summaryOff = R.string.termux_terminal_view_terminal_margin_adjustment_off, +property = ::isViewMarginAdjustmentEnabled, +tooltipTag = PREFS_TERMUX_MARGIN, +) From 033839245c7418be770ddafe97326733c3bbd1e2 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:18:16 -0700 Subject: [PATCH 06/23] ADFA-5088: Remove now-unused coarse tooltip tag constants PREFS_GRADLE and PREFS_DEVELOPER had no remaining references once the preceding commits switched every item to its own tag. Co-Authored-By: Claude Sonnet 5 --- .../main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index 03025ef19d..3060f52c4e 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -45,13 +45,11 @@ object TooltipTag { const val PREFS_GENERAL = "prefs.general" const val PREFS_EDITOR = "prefs.editor" const val PREFS_BUILD_RUN = "prefs.buildrun" - const val PREFS_GRADLE = "prefs.gradle" const val PREFS_TERMUX = "prefs.termux" const val PREFS_GIT = "prefs.git" const val PREFS_PLUGIN_MANAGER = "prefs.pluginmanager" const val PREFS_ABOUT = "prefs.about" const val PREFS_DEVOPTIONS = "prefs.devoptions" - const val PREFS_DEVELOPER = "prefs.developer" // Preferences - General screen const val PREFS_GENERAL_UIMODE = "prefs.general.uimode" From 2669e955f5fc05899b4ef36d94ceeb8480e54d67 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:18:28 -0700 Subject: [PATCH 07/23] ADFA-5088: Add docdb SQL script for new Preferences tooltip tags Adds the Tooltips (40 UPDATEs filling in existing empty stub rows, 25 INSERTs for brand-new tags) and Content (65 INSERTs, Brotli-compressed HTML) rows the tags added in the preceding commits look up. Not applied to assets/documentation.db here - that database is owned by the separate docdb-studio project; this script is the deliverable to run against it. Co-Authored-By: Claude Sonnet 5 --- .../.ADFA-5088-preference-tooltips.sql.swp | Bin 0 -> 16384 bytes docs/docdb/ADFA-5088-preference-tooltips.sql | 535 ++++++++++++++++++ 2 files changed, 535 insertions(+) create mode 100644 docs/docdb/.ADFA-5088-preference-tooltips.sql.swp create mode 100644 docs/docdb/ADFA-5088-preference-tooltips.sql diff --git a/docs/docdb/.ADFA-5088-preference-tooltips.sql.swp b/docs/docdb/.ADFA-5088-preference-tooltips.sql.swp new file mode 100644 index 0000000000000000000000000000000000000000..8252a559134d14df6d67272ddf16f234914acb33 GIT binary patch literal 16384 zcmeHOO^h5z74AR?5O4@T7a&NFg=lw@nI8Wrk+IF9Wq0hXw6+)TY;3qNHPbac?d|UB zban4|qR1$KP`Cg>B0}PZ$OQxkgpjyk1P3l8;lPy(93r@I;m?o&->a^k-EDi>AFaiK z9_4v%X}5QRjzGJnVR4Gw}HO9-kE% z$CEwo;o+NxVS6F=QWbb>S?&dDHC5N;Ak*zd<;lx&cO);Uwx|F)k6d%aT`|5s)FOO^3A z?D!|_^urwnRnS*J&w?K1@3%V6BIuJK7xbqGzz6gY=osksTO8*HphrNzzu$3w z2YLnc0_e3jJI?n&-vzx0`ZVY_=(Aq}eFyY4kPmtVSiA`O2IzSZW3vL<^FyTbC=%&} zK2Ec1GNX|iQzYUE=|l{qCKXG{M4y7#lX0fq*`~9;y0p;kP}jcFKx>_@<6M2JbEShj zeP+ZjK4@2ZL8el7s4y>hR?gDGr6n4OO!_LFEPJ@|#94>PlbHxYp4J@&ng$~g`&c*| zN#bdwhBUyN7L>^w8N@IH1?wh7Qs_LD9$(i9&FO&%Ws7A^q!E~s;D!u!!=-gCNstZ$ zPo|V93=B4KVN-;;q`u6?QpWToiP)o4C*4^HRzZdZD%LGTVc`xA$KEKg6&N$4eba!? zOEqac3NmTTLSCSgP)wReTm{L}QnbDt;T;&jPS-B3v~(f|!7vzrVH$}n3u2!}LQ{~z zvSVPD_gi|9LiMV~i^dGb9awQ=0jqte)P^=Io5kdToWhr}OhxDpR6GpQNWo5fLvnQl z+o-rn{K^gfFg|cWk%qTI5=dv5i7dcalBy`lU{dHTp(&-3SJ5<4c}k(s8Q;RM@VI0= z&JQ->Zy04xP_{}?gWBF zo+-bcJH=LqHWGkmSPOZ{X3g*dN;iP|1RWP$^I(c`(+}Etd?pAfP#bO?dVne`` zuW`Sf`{N+dsr0Wrh_J`@_Aaw?rjQ|Ar7A3f_CSTu5F$nw%9^w!(hUQ| zc@>7pINHGH(vF{`$3Zq2VQ=2WJ+?tRJ8Krka@KUapceAl#OgK4g8=qV2n*L6jJ8{g zSr#&p)$tHm?hPV``qHfoV9lvHi_p&5c&AON*uFLd>k+;>xeT{6c(E1fhfIk`j#4Dc z`;;QoH~9XeShwXcr_DfcNidRONJvH+jtRwMO7obm=Lq%&hD&RmD_stO*x;rF;;E=N zj0fU%P9)`1)X#&^YnqunsVA|+$#q?Nj{2#}!hl+zp?Rzl${rF4iYlJ2WjJJRseC36 zkgRxW$#HRIndV}ZLBcTTr(B}-QP=<#6f?_1>bZr3_6S82f`RCV(qm?MJU{|WrPc`L zPjxS@@C}=R4sb(JKjAl$evlw|ZE@kkr+1};w^x@}I&+Pl(K3pV^PxCg$|f@@uv2== z1t_fGbjoZyi<0&Yx1Y{7ddMEE4KwLNsYL_`UiAZsHba_0c+;mD2u}RnF?Z4 zrYsP%LtSPu$LqE^Tg0S&*Xz?c+WXyGa9Aj|h%Y#o8_ZKFLaMSmV;4km!LoNJi7blN zlQcKOp})-n&or%pTU)3%To!ZzUd2vpGQrNW7YtB_&NQ~Rd%+pkJC|Cy#)w^9gkv$$ z6w5Icm}>&-80D)>TkyPS3awenY#f-~8Y|<1+M2o)si+oFs3TUu<=|N`JoD#o9|1`a zN=x{6v7Wng0Zd^=<(_rtwiM%1)o-w~tzTOH)OyD-#P*T^(6iiVtn$!wa`h2qR0 zHMgzOo<(e)HUzVcv#^iJln}KD52n_Kgabrz9iUiadDax7f3Ije9_z6n&@O~oy$Kmi z*zrrDxwxqW7Vuy;X163L85$%Q2*A>WkTPodfGH0}O1+?MmTANHdz6T5RH)DmP=lYK zo>iqskCVA{l6r+cJ@5n$qDVFz+{#S>%h8;<`4kJW!w-?D(0vI+cw^rJ45J=b;glz5 zz;UV=bAn^A{>%wJmX%GKy{9XDclAo={Mu(9<60F_EiTL8!cRCvA&~n46`qFC@P)pn}q1EZ@?wX|~WbaYgX;^@#`}53}(2s8iLYp$?PJUc;8B4eh z_y5<>EB_3{{eSuV`6_z;Tj=qB3VINP*905|`WAZn`#=upDth^s(ZBx+^mFvF<3G5vqDbZcvFYdY5yu#K~h zt*^`KG@0+jCJC-6f6XjnL^N$L4lIx@Q>b&7<0&vEm1jwgW4*GsN0(NdIHA=*g%N9{ z>M~UlPuL9PSf=i|b~1l1nK#Wg3M(AQ^e3o~%pn6>zdVgG3x^X!3WnTKpm;!Wn9&$% zoev?jERJbdxpEmuYI-~7eb;Jxe;7H=Jv5}3M+wV(2s@TjcQxY@cr(VD#wj1Dnhv01 zxd6VG#%K!6eE?FJ){U#en{9XC8&8`W&UAt!EG48SJ$tgB`#u-X(*qAauGnck^aVMZR8;w}9x)m`fs1ec%>jacb9~{=JGE5h z90d&ro-SY;ne&iT@U~?He%65gK=avroJW0FnQZ|Ecs^9XzaIxLg*|%A zk}!u$;h`5AzkO$p9g}v&aMiG;iv{mH&_m8J2SIo$hRDtx9-3M{mB2AL4wGc0HgUnA z#ZNeVJnBMo&eC$!LNLN91l+l5B-40dC`HtgBEzix!(7$t=;8K*tc@K5?eN%dzpjc& zI2W;#c7=N#cc8WB$BBp!MPx623eV#cF" | brotli -Z > /tmp/x.br` +-- immediately before each `INSERT ... READFILE('/tmp/x.br')` so the +-- uncompressed HTML is visible in this script. `.system` and `READFILE()` +-- require the sqlite3 CLI (not a library binding). If `.system` is disabled +-- in your sqlite3 build, run the `echo ... | brotli -Z > file` line yourself +-- via a shell first, then run just the INSERT statements. + +-- --------------------------------------------------------------------- +-- Tooltips: UPDATEs (existing empty stub rows) +-- --------------------------------------------------------------------- + +UPDATE Tooltips SET + summary = 'Change general settings for the IDE.', + detail = 'Set the app theme, language, and how Code on the Go opens projects. These settings apply across the whole app.' +WHERE tag = 'prefs.general' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Choose light mode, dark mode, or match your device''s system setting.', + detail = 'This setting controls the color theme of the app. Pick Light, Dark, or Follow system. Follow system switches automatically when your device''s theme changes.' +WHERE tag = 'prefs.general.uimode' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Choose the display language for Code on the Go.', + detail = 'This setting changes the language of the app menus and text. Pick System Default to use your device language, or choose a specific language from the list.' +WHERE tag = 'prefs.general.language' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Reopen your last project automatically when the app starts.', + detail = 'When on, Code on the Go opens the last project you worked on as soon as it starts. When off, the app shows the project list instead.' +WHERE tag = 'prefs.general.openlast' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Ask for confirmation before opening your last project.', + detail = 'When on, Code on the Go shows a confirmation prompt before it reopens your last project. This gives you a chance to go to the project list instead.' +WHERE tag = 'prefs.general.confirmopen' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Change how the code editor looks and behaves.', + detail = 'Set font size, tab size, whitespace display, and other editor behavior. XML-specific formatting has its own sub-screen.' +WHERE tag = 'prefs.editor' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Set the editor text size.', + detail = 'This changes the size of code text in the editor, measured in sp (scale-independent pixels). Use a slider to pick a value between 6 and 32.' +WHERE tag = 'prefs.editor.fontsize' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Set how many spaces one tab indents.', + detail = 'This sets the number of spaces the editor uses for each indent level. Choose from 2, 4, 6, or 8 spaces.' +WHERE tag = 'prefs.editor.tabsize' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Choose which whitespace and line-break marks the editor shows.', + detail = 'This opens a dialog with checkboxes for leading, trailing, inner, and empty-line whitespace, plus line-break marks. Turn on any you want the editor to display.' +WHERE tag = 'prefs.editor.nonprinting' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Insert spaces instead of a tab character when you press Tab.', + detail = 'When on, pressing the Tab key inserts spaces. When off, it inserts a tab character.' +WHERE tag = 'prefs.editor.softtab' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Break long lines so they fit on the screen.', + detail = 'When on, the editor wraps long lines onto multiple visual lines instead of scrolling sideways. This does not change the file content.' +WHERE tag = 'prefs.editor.wordwrap' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Show a magnified view of text while you select it.', + detail = 'When on, pressing and holding on text shows a zoomed-in view of the area around your finger. This makes it easier to place the cursor precisely.' +WHERE tag = 'prefs.editor.magnifier' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Use spaces to define word boundaries when you double-tap to select.', + detail = 'When on, double-tapping a word selects text up to the nearest space. When off, the editor also uses punctuation to decide where a word ends.' +WHERE tag = 'prefs.editor.wordboundaries' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Match code suggestions even when letter case differs.', + detail = 'When on, autocomplete suggestions match class and member names regardless of whether you type upper or lower case letters.' +WHERE tag = 'prefs.editor.matchcase' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Delete a whole blank line with one backspace.', + detail = 'When on, pressing backspace on a line with no visible text removes the entire line at once.' +WHERE tag = 'prefs.editor.deletelines' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Delete a full indent level with one backspace.', + detail = 'When on, pressing backspace at the start of an indented line removes the whole indent level at once, instead of one character at a time.' +WHERE tag = 'prefs.editor.smartbackspace' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Keep the current code block header visible while scrolling.', + detail = 'When on, the editor pins the header line of the current class or method at the top of the screen while you scroll through its body.' +WHERE tag = 'prefs.editor.stickyscroll' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Keep line numbers visible when scrolling sideways.', + detail = 'When on, line numbers stay in place on the left side of the screen even when you scroll a long line horizontally.' +WHERE tag = 'prefs.editor.pinlines' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Format Java code using Google''s style rules.', + detail = 'When on, the code formatter applies Google''s Java style conventions, such as its indentation and spacing rules, instead of the default style.' +WHERE tag = 'prefs.editor.googlestyle' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Open XML-specific formatting settings.', + detail = 'This opens a sub-screen with formatting options that apply only to XML files, such as attribute placement and line width.' +WHERE tag = 'prefs.editor.xml' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Change settings for building and running your app.', + detail = 'Set additional Gradle flags and control whether the app launches automatically after a run installs it.' +WHERE tag = 'prefs.buildrun' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Launch the app automatically after a successful run installs it.', + detail = 'When on, Code on the Go opens your app right after installing it, with no extra confirmation step.' +WHERE tag = 'prefs.buildrun.autolaunch' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Choose extra Gradle flags to add to every build.', + detail = 'This opens a dialog with checkboxes for common Gradle command-line flags, such as --info or --offline. Any flag you turn on is added to every Gradle task the IDE runs.' +WHERE tag = 'prefs.buildrun.flags' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Change settings for the built-in terminal.', + detail = 'Set the terminal logging level, keyboard behavior, and screen margin.' +WHERE tag = 'prefs.termux' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Set how much detail the terminal writes to its internal log.', + detail = 'This controls the terminal own internal logging level, used for troubleshooting the terminal itself. Higher levels record more detail.' +WHERE tag = 'prefs.termux.loglevel' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Log every key you press in the terminal, for debugging.', + detail = 'When on, the terminal records each key press to the system log. This is very verbose and can slow the app down, so leave it off unless you are debugging a keyboard issue.' +WHERE tag = 'prefs.termux.keylogging' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Adjust the terminal margin so the on-screen keyboard does not cover it.', + detail = 'When on, the terminal adjusts its margin to avoid being covered by the on-screen keyboard. If you notice screen flickering, turn this off.' +WHERE tag = 'prefs.termux.margin' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Show the on-screen keyboard only when no physical keyboard is connected.', + detail = 'When on, the terminal hides its on-screen keyboard while a hardware keyboard is connected, and shows it again once the hardware keyboard is disconnected.' +WHERE tag = 'prefs.termux.nohardkeyboard' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Show the on-screen keyboard in the terminal.', + detail = 'When on, the terminal shows its own on-screen keyboard for typing commands. This is on by default.' +WHERE tag = 'prefs.termux.softkeyboard' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Put a tag closing bracket on its own new line.', + detail = 'When on, the formatter places the final ">" or "/>" on a new line after the last attribute. This is off by default.' +WHERE tag = 'prefs.xml.closebracket' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Choose how the formatter writes empty XML elements.', + detail = 'Pick Expand to write empty elements as an open and a close tag, Collapse to write them as a single self-closing tag, or Ignore to leave them as they are.' +WHERE tag = 'prefs.xml.emptyelements' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Set the maximum characters allowed on one line.', + detail = 'This sets the line-width limit, in characters, before the formatter wraps a line onto more than one line. The default is 80.' +WHERE tag = 'prefs.xml.maxlinewidth' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Keep existing line breaks between attributes.', + detail = 'When on, the formatter leaves attributes on the lines you already put them on, instead of joining them onto one line. This is on by default.' +WHERE tag = 'prefs.xml.preserveattributes' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Set how many blank lines to keep between elements.', + detail = 'This sets the maximum number of blank lines the formatter keeps between elements when it reformats a file. Extra blank lines beyond this number are removed. The default is 2.' +WHERE tag = 'prefs.xml.preservenewlines' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Add a space before a tag self-closing slash.', + detail = 'When on, the formatter writes a space before "/>", producing "" instead of "". This is on by default.' +WHERE tag = 'prefs.xml.spacebeforeclose' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Set the indent size for attributes on split lines.', + detail = 'This sets how many spaces the formatter uses to indent an attribute placed on its own line. By default it is half the editor tab size.' +WHERE tag = 'prefs.xml.splitattribindent' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Remove extra spaces at the end of lines.', + detail = 'When on, the formatter deletes any whitespace left at the end of each line. This is on by default.' +WHERE tag = 'prefs.xml.trimwhitespace' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Change experimental and debugging settings.', + detail = 'These settings are for troubleshooting Code on the Go itself, not for normal project configuration. Only change them if you are diagnosing a problem.' +WHERE tag = 'prefs.devoptions' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Save the IDE internal logs to a file.', + detail = 'When on, Code on the Go writes its internal logs to a file at $HOME/.cg/logs, for troubleshooting.' +WHERE tag = 'prefs.devoptions.dumplogs' AND categoryId = 1; + +UPDATE Tooltips SET + summary = 'Show logs from your running app inside Code on the Go.', + detail = 'When on, Code on the Go displays log output from apps you run. Turn this off to stop showing those logs.' +WHERE tag = 'prefs.devoptions.logsender' AND categoryId = 1; + +-- --------------------------------------------------------------------- +-- Tooltips: INSERTs (brand new tags) +-- --------------------------------------------------------------------- + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.leading', + 'Show whitespace at the start of each line.', + 'When on, the editor marks spaces and tabs that come before the first visible character on a line.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.trailing', + 'Show whitespace at the end of each line.', + 'When on, the editor marks spaces and tabs that come after the last visible character on a line.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.inner', + 'Show whitespace between words.', + 'When on, the editor marks spaces and tabs that appear between two visible characters on a line.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.emptylines', + 'Show whitespace on lines with no visible text.', + 'When on, the editor marks spaces and tabs on lines that contain only whitespace.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.linebreaks', + 'Show a mark at the end of each line.', + 'When on, the editor draws a small symbol where each line ends, so line breaks are visible.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.trimfinalnewline', + 'Remove blank lines at the end of an XML file.', + 'When on, the formatter deletes empty lines at the end of the file, leaving no trailing blank lines. This is off by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.insertfinalnewline', + 'Add one newline at the end of an XML file.', + 'When on, the formatter makes sure the file ends with exactly one newline character. This is on by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.splitattributes', + 'Put each XML attribute on its own line.', + 'When on, the formatter places every attribute of a tag on a separate line instead of one line. This is on by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincdatalines', + 'Combine multiple CDATA lines into one.', + 'When on, the formatter merges the lines of a multi-line CDATA section into a single line. This is off by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincommentlines', + 'Combine multiple single-line comments into one.', + 'When on, the formatter merges adjacent single-line XML comments into a single line. This is on by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincontentlines', + 'Combine multiple lines of element text into one.', + 'When on, the formatter merges multiple lines of text inside an XML element into a single line. This is off by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preserveemptycontent', + 'Keep tags that stand alone on an empty line as they are.', + 'When on, the formatter leaves an element alone on its own blank line unchanged, instead of collapsing it. This is on by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.stacktrace', + 'Show a full stack trace when a build fails.', + 'Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure instead of a short summary.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.info', + 'Show detailed log messages for each build task.', + 'Sets the Gradle --info flag. Gradle prints detailed log messages as each task runs, instead of just a summary line for each task.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.debug', + 'Show maximum detail in build logs.', + 'Sets the Gradle --debug flag. Gradle prints very detailed, low-level log messages for every task. This produces a large amount of output and can slow the build down.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.scan', + 'Publish a Gradle Build Scan report link.', + 'Sets the Gradle --scan flag. After the build, Gradle uploads build data and gives you a link to a Build Scan report with detailed information.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.warningmodeall', + 'Show every deprecation warning during the build.', + 'Sets the Gradle --warning-mode all flag. Gradle lists every individual deprecation warning instead of just a count at the end of the build.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.buildcache', + 'Reuse outputs from a previous build to speed up this one.', + 'Sets the Gradle --build-cache flag. Gradle reuses task outputs from an earlier build when the inputs have not changed, which can make the build faster.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.offline', + 'Build without any network access.', + 'Sets the Gradle --offline flag. Gradle uses only dependencies already downloaded to your device and does not try to reach the network.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.crashreports', + 'Show a notification if the terminal crashes.', + 'When on, Code on the Go shows a notification with a crash report after the terminal process crashes. This is on by default.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git', + 'Set your Git author identity.', + 'This screen sets the name and email address recorded as the author of your Git commits.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.username', + 'Set the name used as the author of your commits.', + 'This name is recorded on every Git commit you make from Code on the Go.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.useremail', + 'Set the email address used as the author of your commits.', + 'This email address is recorded on every Git commit you make from Code on the Go.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.pluginmanager', + 'Manage IDE plugins and extensions.', + 'This opens Plugin Manager, where you can install, remove, and configure plugins that add features to Code on the Go.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about', + 'See app version and other information about Code on the Go.', + 'This opens the About screen, with details such as the app version and credits.'); + +-- --------------------------------------------------------------------- +-- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. +-- Each pair shows the uncompressed HTML via `.system echo ... | brotli -Z` +-- then inserts the resulting compressed file with READFILE(). +-- --------------------------------------------------------------------- + +.system echo "

The General screen holds settings that are not specific to the editor or to a single project. Use it to set the app theme, choose a display language, and control how Code on the Go opens the last project on launch.

Changes here apply immediately and affect the whole app.

" | brotli -Z > /tmp/adfa5088-prefs-general.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general', 1, 12, READFILE('/tmp/adfa5088-prefs-general.br')); + +.system echo "

UI mode sets the color theme for Code on the Go.

  • Light: always use light colors.
  • Dark: always use dark colors.
  • Follow system: match your device's current theme, and switch automatically when it changes.
" | brotli -Z > /tmp/adfa5088-prefs-general-uimode.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/uimode', 1, 12, READFILE('/tmp/adfa5088-prefs-general-uimode.br')); + +.system echo "

Language sets the display language for the Code on the Go interface.

Choose System Default to use your device language setting, or pick one of the supported languages directly. The app restarts the affected screens to apply the change.

" | brotli -Z > /tmp/adfa5088-prefs-general-language.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/language', 1, 12, READFILE('/tmp/adfa5088-prefs-general-language.br')); + +.system echo "

Open last project controls what happens when you start Code on the Go.

Turn it on to skip the project list and go straight into your most recent project. Turn it off to see the project list every time.

" | brotli -Z > /tmp/adfa5088-prefs-general-openlast.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/openlast', 1, 12, READFILE('/tmp/adfa5088-prefs-general-openlast.br')); + +.system echo "

Confirm project opening adds a confirmation step before Code on the Go reopens your last project automatically.

Use this if you often want to switch to a different project instead of continuing the last one.

" | brotli -Z > /tmp/adfa5088-prefs-general-confirmopen.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/confirmopen', 1, 12, READFILE('/tmp/adfa5088-prefs-general-confirmopen.br')); + +.system echo "

The Editor screen holds settings for the code editor: font size, tab size, word wrap, whitespace display, and other editing behavior.

Formatting options specific to XML files are on a separate sub-screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor', 1, 12, READFILE('/tmp/adfa5088-prefs-editor.br')); + +.system echo "

Font size sets the text size used in the code editor, in sp units.

Choose a larger value to make code easier to read, or a smaller value to fit more code on the screen. The allowed range is 6 to 32.

" | brotli -Z > /tmp/adfa5088-prefs-editor-fontsize.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/fontsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-fontsize.br')); + +.system echo "

Tab size sets the number of spaces that one tab character represents in the editor.

Pick a value that matches your project code style: 2, 4, 6, or 8 spaces.

" | brotli -Z > /tmp/adfa5088-prefs-editor-tabsize.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/tabsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-tabsize.br')); + +.system echo "

Show non-printing characters controls which invisible characters, such as spaces, tabs, and line breaks, the editor marks visibly.

Open this setting to choose which kinds to show: leading whitespace, trailing whitespace, whitespace between words, whitespace on empty lines, and line-break marks.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting.br')); + +.system echo "

Leading marks whitespace characters, such as spaces and tabs, that appear before the first visible character on a line.

Turn this on to see indentation whitespace clearly.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-leading.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/leading', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-leading.br')); + +.system echo "

Trailing marks whitespace characters that appear after the last visible character on a line.

Turn this on to spot stray trailing spaces or tabs.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-trailing.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/trailing', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-trailing.br')); + +.system echo "

Inner marks whitespace characters that appear between words or tokens within a line, rather than at the start or end.

Turn this on to check for irregular spacing inside a line.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-inner.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/inner', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-inner.br')); + +.system echo "

Empty lines marks whitespace characters on lines that have no visible text, only spaces or tabs.

Turn this on to spot stray whitespace on otherwise blank lines.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-emptylines.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/emptylines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-emptylines.br')); + +.system echo "

Line breaks draws a small marker at the end of each line to show where the line break occurs.

Turn this on to see line endings clearly, for example when comparing files with different line-ending styles.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/linebreaks', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br')); + +.system echo "

Use soft tab controls what the editor inserts when you press the Tab key.

Turn this on to insert spaces instead of a tab character. This can help keep code consistent across editors that render tabs differently.

" | brotli -Z > /tmp/adfa5088-prefs-editor-softtab.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/softtab', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-softtab.br')); + +.system echo "

Word wrap breaks long lines of code into multiple visual lines so they fit within the visible width of the editor.

The underlying file is not changed; only the way it is displayed changes.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordwrap.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordwrap', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordwrap.br')); + +.system echo "

Enable magnifier shows a zoomed-in view of the text around your finger when you press and hold to select text.

This makes it easier to position the cursor precisely on a small screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor-magnifier.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/magnifier', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-magnifier.br')); + +.system echo "

Use spaces as word boundaries changes how the editor decides what counts as one word when you double-tap to select text.

When on, only blank space marks the edge of a word. When off, the editor also treats punctuation, such as periods and underscores, as word boundaries.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordboundaries.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordboundaries', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordboundaries.br')); + +.system echo "

Match completions in lower case controls whether the code editor autocomplete feature is case-sensitive.

When on, typing in lower case still matches suggestions that use upper case letters, such as class names.

" | brotli -Z > /tmp/adfa5088-prefs-editor-matchcase.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/matchcase', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-matchcase.br')); + +.system echo "

Delete empty lines on backspace changes what happens when you press backspace on a line with no visible text.

When on, the whole empty line is removed in one step, instead of removing one whitespace character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-deletelines.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/deletelines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-deletelines.br')); + +.system echo "

Smart backspace indent changes what one backspace press removes when the cursor is inside leading indentation.

When on, it removes a full indent level at once. When off, it removes one character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-smartbackspace.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/smartbackspace', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-smartbackspace.br')); + +.system echo "

Sticky scroll keeps the header line of the code block you are inside, such as a class or method declaration, visible at the top of the editor while you scroll down through its contents.

This helps you keep track of context in long files.

" | brotli -Z > /tmp/adfa5088-prefs-editor-stickyscroll.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/stickyscroll', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-stickyscroll.br')); + +.system echo "

Pin line numbers keeps the line-number column fixed on the left side of the editor.

Without this, scrolling a long line horizontally can move the line numbers out of view along with the code.

" | brotli -Z > /tmp/adfa5088-prefs-editor-pinlines.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/pinlines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-pinlines.br')); + +.system echo "

Use Google Java Style code formatting applies Google's published conventions for Java source code, such as indentation, spacing, and line-wrapping rules, when you format code.

Turn this on if your project follows Google's Java style guide.

" | brotli -Z > /tmp/adfa5088-prefs-editor-googlestyle.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/googlestyle', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-googlestyle.br')); + +.system echo "

XML formatting options opens a sub-screen of settings that control how Code on the Go formats XML files, separate from the general editor settings.

" | brotli -Z > /tmp/adfa5088-prefs-editor-xml.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/xml', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-xml.br')); + +.system echo "

Trim final new line removes empty lines at the very end of an XML file when the formatter runs.

This is off by default.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimfinalnewline.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimfinalnewline.br')); + +.system echo "

Insert final new line adds a single newline character at the end of an XML file if one is not already there.

Many tools expect files to end this way.

" | brotli -Z > /tmp/adfa5088-prefs-xml-insertfinalnewline.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/insertfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-insertfinalnewline.br')); + +.system echo "

Split attributes places each attribute of an XML tag on its own line when the formatter runs.

This makes tags with many attributes easier to read and to compare in version control.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattributes.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattributes.br')); + +.system echo "

Join CDATA lines combines the lines of a CDATA section into a single line when the formatter runs.

CDATA sections hold raw, unescaped text or markup inside an XML file.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincdatalines.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincdatalines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincdatalines.br')); + +.system echo "

Join comment lines combines adjacent single-line XML comments into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincommentlines.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincommentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincommentlines.br')); + +.system echo "

Join content lines combines multiple lines of text inside a single XML element into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincontentlines.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincontentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincontentlines.br')); + +.system echo "

Space before empty close tag adds one space before the closing /> of a self-closing XML tag, so the formatter produces <foo /> instead of <foo/>.

" | brotli -Z > /tmp/adfa5088-prefs-xml-spacebeforeclose.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/spacebeforeclose', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-spacebeforeclose.br')); + +.system echo "

Preserve empty content keeps an XML element that appears alone on an otherwise empty line as it is, instead of collapsing or moving it during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveemptycontent.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveemptycontent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveemptycontent.br')); + +.system echo "

Preserve attribute line breaks keeps attributes on the separate lines you already placed them on, instead of collapsing them onto a single line during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveattributes.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveattributes.br')); + +.system echo "

Closing bracket on new line places the final closing bracket of a tag, including self-closing tags, on its own new line after the last attribute.

" | brotli -Z > /tmp/adfa5088-prefs-xml-closebracket.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/closebracket', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-closebracket.br')); + +.system echo "

Trim trailing whitespace removes any spaces or tabs left at the end of each line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimwhitespace.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimwhitespace', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimwhitespace.br')); + +.system echo "

Maximum line width sets the number of characters allowed on a single line before the formatter wraps it onto additional lines.

The default value is 80 characters.

" | brotli -Z > /tmp/adfa5088-prefs-xml-maxlinewidth.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/maxlinewidth', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-maxlinewidth.br')); + +.system echo "

Preserve new lines sets the maximum number of consecutive blank lines the formatter keeps between XML elements.

Any blank lines beyond this limit are removed. The default is 2.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preservenewlines.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preservenewlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preservenewlines.br')); + +.system echo "

Split attributes indent size sets the number of spaces used to indent an attribute placed on its own line by the formatter.

By default, this is half the editor's tab size.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattribindent.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattribindent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattribindent.br')); + +.system echo "

Empty elements behavior controls how the formatter writes an XML element that has no content.

  • Expand: write a separate open and close tag, for example <foo></foo>.
  • Collapse: write a single self-closing tag, for example <foo/>.
  • Ignore: leave the element as it is in the source file.

The default is Collapse.

" | brotli -Z > /tmp/adfa5088-prefs-xml-emptyelements.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/emptyelements', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-emptyelements.br')); + +.system echo "

The Build & Run screen holds settings for the Gradle build and for running your app: additional Gradle command-line flags, and whether to launch the app automatically after installation.

" | brotli -Z > /tmp/adfa5088-prefs-build.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build', 1, 12, READFILE('/tmp/adfa5088-prefs-build.br')); + +.system echo "

Launch app after installation controls whether Code on the Go opens your app automatically once a run finishes installing it on the device.

When off, you need to launch the app yourself.

" | brotli -Z > /tmp/adfa5088-prefs-build-autolaunch.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/autolaunch', 1, 12, READFILE('/tmp/adfa5088-prefs-build-autolaunch.br')); + +.system echo "

Additional Gradle flags lets you turn on extra command-line flags that Code on the Go adds to every Gradle task it runs, such as build and sync.

Use this to get more detailed logs, use a build cache, or work offline, without typing the flags yourself each time.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags.br')); + +.system echo "

Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure, which can help diagnose the exact cause.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-stacktrace.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--stacktrace', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-stacktrace.br')); + +.system echo "Sets the Gradle --info flag to produce more detailed log messages as each task runs, rather than just a summary line for each task." | brotli -Z > /tmp/adfa5088-prefs-build-flags-info.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--info', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-info.br')); + +.system echo "

Sets the Gradle --debug flag. Gradle prints its most detailed log level for every task.

This produces a lot of output and can make the build noticeably slower. Use it only when you need to diagnose a specific problem.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-debug.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--debug', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-debug.br')); + +.system echo "

Sets the Gradle --scan flag. After the build finishes, Gradle uploads data about the build and gives you a link to a Build Scan report.

The report shows detailed information about tasks, dependencies, and timing. This requires network access.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-scan.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--scan', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-scan.br')); + +.system echo "

Sets the Gradle --warning-mode all flag. Gradle prints every individual deprecation warning during the build, instead of only a summary count at the end.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-warningmodeall.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--warning-mode-all', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-warningmodeall.br')); + +.system echo "

Sets the Gradle --build-cache flag. Gradle reuses outputs from a previous build for any task whose inputs have not changed, instead of running that task again.

This can noticeably speed up repeated builds.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-buildcache.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--build-cache', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-buildcache.br')); + +.system echo "

Sets the Gradle --offline flag. Gradle uses only the dependencies already downloaded to your device and does not attempt any network access.

The build fails if a required dependency has not been downloaded yet.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-offline.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--offline', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-offline.br')); + +.system echo "

The Terminal screen holds settings for the built-in terminal emulator: internal logging, keyboard behavior, crash notifications, and screen margin.

" | brotli -Z > /tmp/adfa5088-prefs-termux.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux', 1, 12, READFILE('/tmp/adfa5088-prefs-termux.br')); + +.system echo "

Log Level sets how much detail the terminal emulator writes to its own internal log, separate from your app logs.

Use a higher level only when troubleshooting a terminal problem, since it produces more log output.

" | brotli -Z > /tmp/adfa5088-prefs-termux-loglevel.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/loglevel', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-loglevel.br')); + +.system echo "

Terminal View Key Logging records each key you press in the terminal to the system log.

This produces a large amount of log output and can cause performance problems, so it is off by default. Turn it on only to debug a keyboard-related issue.

" | brotli -Z > /tmp/adfa5088-prefs-termux-keylogging.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/keylogging', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-keylogging.br')); + +.system echo "

Crash Report Notifications controls whether Code on the Go shows a notification after the terminal process crashes.

The notification lets you view or share a report about the crash. This is on by default.

" | brotli -Z > /tmp/adfa5088-prefs-termux-crashreports.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/crashreports', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-crashreports.br')); + +.system echo "

Soft Keyboard Enabled turns on the terminal's own on-screen keyboard, which includes keys not found on a standard keyboard, such as Ctrl and Esc.

" | brotli -Z > /tmp/adfa5088-prefs-termux-softkeyboard.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/softkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-softkeyboard.br')); + +.system echo "

Soft Keyboard Only If No Hardware shows the on-screen keyboard only when no physical keyboard is connected to your device.

This is off by default, meaning the on-screen keyboard can show even with a hardware keyboard connected.

" | brotli -Z > /tmp/adfa5088-prefs-termux-nohardkeyboard.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/nohardkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-nohardkeyboard.br')); + +.system echo "

Terminal Margin Adjustment changes the terminal view margin to try to prevent the on-screen keyboard from covering part of the terminal or its extra keys row.

This is on by default. If it causes screen flickering on your device, turn it off.

" | brotli -Z > /tmp/adfa5088-prefs-termux-margin.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/margin', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-margin.br')); + +.system echo "

The Git screen sets your author identity for Git: the name and email address recorded on every commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git', 1, 12, READFILE('/tmp/adfa5088-prefs-git.br')); + +.system echo "

User name sets the name recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-username.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/username', 1, 12, READFILE('/tmp/adfa5088-prefs-git-username.br')); + +.system echo "

User email sets the email address recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-useremail.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/useremail', 1, 12, READFILE('/tmp/adfa5088-prefs-git-useremail.br')); + +.system echo "

Plugin Manager opens a screen where you can browse, install, remove, and configure plugins that extend Code on the Go with extra features.

" | brotli -Z > /tmp/adfa5088-prefs-pluginmanager.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/pluginmanager', 1, 12, READFILE('/tmp/adfa5088-prefs-pluginmanager.br')); + +.system echo "

About Code on the Go opens a screen with details about the app, such as its version number and credits.

" | brotli -Z > /tmp/adfa5088-prefs-about.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/about', 1, 12, READFILE('/tmp/adfa5088-prefs-about.br')); + +.system echo "

Developer Options holds experimental and debugging settings for Code on the Go, such as log dumping and log sending controls.

These settings are meant for troubleshooting, not everyday use.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions.br')); + +.system echo "

Dump logs writes the Code on the Go internal logs to a file at ~/.cg/logs, inside your home directory.

Turn this on when you need to collect logs to diagnose a problem.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-dumplogs.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/dumplogs', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-dumplogs.br')); + +.system echo "

Enable LogSender controls whether Code on the Go shows log output from the apps you run, inside its own log viewer.

This is on by default. Turn it off if you do not want to see app logs inside the IDE.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-logsender.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-logsender.br')); From e9ae4ff170137d38cc9281cf7d0066a5382f1e47 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:56:52 -0700 Subject: [PATCH 08/23] ADFA-5088: Remove accidentally-committed vim swap file .ADFA-5088-preference-tooltips.sql.swp was a leftover editor artifact picked up by `git add docs/docdb/` alongside the real SQL script. Co-Authored-By: Claude Sonnet 5 --- .../.ADFA-5088-preference-tooltips.sql.swp | Bin 16384 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/docdb/.ADFA-5088-preference-tooltips.sql.swp diff --git a/docs/docdb/.ADFA-5088-preference-tooltips.sql.swp b/docs/docdb/.ADFA-5088-preference-tooltips.sql.swp deleted file mode 100644 index 8252a559134d14df6d67272ddf16f234914acb33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeHOO^h5z74AR?5O4@T7a&NFg=lw@nI8Wrk+IF9Wq0hXw6+)TY;3qNHPbac?d|UB zban4|qR1$KP`Cg>B0}PZ$OQxkgpjyk1P3l8;lPy(93r@I;m?o&->a^k-EDi>AFaiK z9_4v%X}5QRjzGJnVR4Gw}HO9-kE% z$CEwo;o+NxVS6F=QWbb>S?&dDHC5N;Ak*zd<;lx&cO);Uwx|F)k6d%aT`|5s)FOO^3A z?D!|_^urwnRnS*J&w?K1@3%V6BIuJK7xbqGzz6gY=osksTO8*HphrNzzu$3w z2YLnc0_e3jJI?n&-vzx0`ZVY_=(Aq}eFyY4kPmtVSiA`O2IzSZW3vL<^FyTbC=%&} zK2Ec1GNX|iQzYUE=|l{qCKXG{M4y7#lX0fq*`~9;y0p;kP}jcFKx>_@<6M2JbEShj zeP+ZjK4@2ZL8el7s4y>hR?gDGr6n4OO!_LFEPJ@|#94>PlbHxYp4J@&ng$~g`&c*| zN#bdwhBUyN7L>^w8N@IH1?wh7Qs_LD9$(i9&FO&%Ws7A^q!E~s;D!u!!=-gCNstZ$ zPo|V93=B4KVN-;;q`u6?QpWToiP)o4C*4^HRzZdZD%LGTVc`xA$KEKg6&N$4eba!? zOEqac3NmTTLSCSgP)wReTm{L}QnbDt;T;&jPS-B3v~(f|!7vzrVH$}n3u2!}LQ{~z zvSVPD_gi|9LiMV~i^dGb9awQ=0jqte)P^=Io5kdToWhr}OhxDpR6GpQNWo5fLvnQl z+o-rn{K^gfFg|cWk%qTI5=dv5i7dcalBy`lU{dHTp(&-3SJ5<4c}k(s8Q;RM@VI0= z&JQ->Zy04xP_{}?gWBF zo+-bcJH=LqHWGkmSPOZ{X3g*dN;iP|1RWP$^I(c`(+}Etd?pAfP#bO?dVne`` zuW`Sf`{N+dsr0Wrh_J`@_Aaw?rjQ|Ar7A3f_CSTu5F$nw%9^w!(hUQ| zc@>7pINHGH(vF{`$3Zq2VQ=2WJ+?tRJ8Krka@KUapceAl#OgK4g8=qV2n*L6jJ8{g zSr#&p)$tHm?hPV``qHfoV9lvHi_p&5c&AON*uFLd>k+;>xeT{6c(E1fhfIk`j#4Dc z`;;QoH~9XeShwXcr_DfcNidRONJvH+jtRwMO7obm=Lq%&hD&RmD_stO*x;rF;;E=N zj0fU%P9)`1)X#&^YnqunsVA|+$#q?Nj{2#}!hl+zp?Rzl${rF4iYlJ2WjJJRseC36 zkgRxW$#HRIndV}ZLBcTTr(B}-QP=<#6f?_1>bZr3_6S82f`RCV(qm?MJU{|WrPc`L zPjxS@@C}=R4sb(JKjAl$evlw|ZE@kkr+1};w^x@}I&+Pl(K3pV^PxCg$|f@@uv2== z1t_fGbjoZyi<0&Yx1Y{7ddMEE4KwLNsYL_`UiAZsHba_0c+;mD2u}RnF?Z4 zrYsP%LtSPu$LqE^Tg0S&*Xz?c+WXyGa9Aj|h%Y#o8_ZKFLaMSmV;4km!LoNJi7blN zlQcKOp})-n&or%pTU)3%To!ZzUd2vpGQrNW7YtB_&NQ~Rd%+pkJC|Cy#)w^9gkv$$ z6w5Icm}>&-80D)>TkyPS3awenY#f-~8Y|<1+M2o)si+oFs3TUu<=|N`JoD#o9|1`a zN=x{6v7Wng0Zd^=<(_rtwiM%1)o-w~tzTOH)OyD-#P*T^(6iiVtn$!wa`h2qR0 zHMgzOo<(e)HUzVcv#^iJln}KD52n_Kgabrz9iUiadDax7f3Ije9_z6n&@O~oy$Kmi z*zrrDxwxqW7Vuy;X163L85$%Q2*A>WkTPodfGH0}O1+?MmTANHdz6T5RH)DmP=lYK zo>iqskCVA{l6r+cJ@5n$qDVFz+{#S>%h8;<`4kJW!w-?D(0vI+cw^rJ45J=b;glz5 zz;UV=bAn^A{>%wJmX%GKy{9XDclAo={Mu(9<60F_EiTL8!cRCvA&~n46`qFC@P)pn}q1EZ@?wX|~WbaYgX;^@#`}53}(2s8iLYp$?PJUc;8B4eh z_y5<>EB_3{{eSuV`6_z;Tj=qB3VINP*905|`WAZn`#=upDth^s(ZBx+^mFvF<3G5vqDbZcvFYdY5yu#K~h zt*^`KG@0+jCJC-6f6XjnL^N$L4lIx@Q>b&7<0&vEm1jwgW4*GsN0(NdIHA=*g%N9{ z>M~UlPuL9PSf=i|b~1l1nK#Wg3M(AQ^e3o~%pn6>zdVgG3x^X!3WnTKpm;!Wn9&$% zoev?jERJbdxpEmuYI-~7eb;Jxe;7H=Jv5}3M+wV(2s@TjcQxY@cr(VD#wj1Dnhv01 zxd6VG#%K!6eE?FJ){U#en{9XC8&8`W&UAt!EG48SJ$tgB`#u-X(*qAauGnck^aVMZR8;w}9x)m`fs1ec%>jacb9~{=JGE5h z90d&ro-SY;ne&iT@U~?He%65gK=avroJW0FnQZ|Ecs^9XzaIxLg*|%A zk}!u$;h`5AzkO$p9g}v&aMiG;iv{mH&_m8J2SIo$hRDtx9-3M{mB2AL4wGc0HgUnA z#ZNeVJnBMo&eC$!LNLN91l+l5B-40dC`HtgBEzix!(7$t=;8K*tc@K5?eN%dzpjc& zI2W;#c7=N#cc8WB$BBp!MPx623eV#cF Date: Wed, 12 Aug 2026 22:58:37 -0700 Subject: [PATCH 09/23] ADFA-5088: Give each Plugin Manager widget its own tooltip tag The Plugin Manager screen's toolbar, download icon, install FAB, empty state, list, plugin row, and row overflow menu all shared one tooltip tag ("plugin.manager"), the same problem this ticket already fixed on the Preferences screen. Give each widget its own tag instead, and add a tooltip to the row's overflow menu button, which previously had none. Co-Authored-By: Claude Sonnet 5 --- .../androidide/activities/PluginManagerActivity.kt | 14 +++++++------- .../androidide/adapters/PluginListAdapter.kt | 6 +++++- .../itsaky/androidide/idetooltips/TooltipTag.kt | 10 +++++++++- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt index a3129fbffb..7f477c69b5 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt @@ -120,7 +120,7 @@ class PluginManagerActivity : EdgeToEdgeIDEActivity() { menuInflater.inflate(R.menu.menu_plugin_manager, menu) binding.toolbar.post { binding.toolbar.findViewById(R.id.action_discover_plugins)?.setOnLongClickListener { view -> - TooltipManager.showIdeCategoryTooltip(this, view, TooltipTag.PLUGIN_MANAGER) + TooltipManager.showIdeCategoryTooltip(this, view, TooltipTag.PLUGIN_MANAGER_DOWNLOAD) true } } @@ -177,23 +177,23 @@ class PluginManagerActivity : EdgeToEdgeIDEActivity() { } private fun setupTooltipLongPress() { - val showTooltip: (View) -> Unit = { view -> - TooltipManager.showIdeCategoryTooltip(this, view, TooltipTag.PLUGIN_MANAGER) + val showTooltip: (View, String) -> Unit = { view, tag -> + TooltipManager.showIdeCategoryTooltip(this, view, tag) } binding.toolbar.setOnLongClickListener { - showTooltip(it) + showTooltip(it, TooltipTag.PLUGIN_MANAGER_TOOLBAR) true } binding.fabInstallPlugin.setOnLongClickListener { - showTooltip(it) + showTooltip(it, TooltipTag.PLUGIN_MANAGER_FAB_INSTALL) true } binding.emptyState.setOnLongClickListener { - showTooltip(it) + showTooltip(it, TooltipTag.PLUGIN_MANAGER_EMPTY_STATE) true } binding.recyclerView.setOnLongClickListener { - showTooltip(it) + showTooltip(it, TooltipTag.PLUGIN_MANAGER_LIST) true } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt index a0a39f460b..769addb453 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt @@ -115,6 +115,10 @@ class PluginListAdapter( btnMenu.setOnClickListener { view -> showPopupMenu(view, plugin) } + btnMenu.setOnLongClickListener { + TooltipManager.showIdeCategoryTooltip(it.context, it, TooltipTag.PLUGIN_MANAGER_ITEM_MENU) + true + } // Setup item click for details root.setOnClickListener { @@ -123,7 +127,7 @@ class PluginListAdapter( // Long-press for Plugin Manager tooltip root.setOnLongClickListener { - TooltipManager.showIdeCategoryTooltip(it.context, it, TooltipTag.PLUGIN_MANAGER) + TooltipManager.showIdeCategoryTooltip(it.context, it, TooltipTag.PLUGIN_MANAGER_ITEM) true } } diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index 3060f52c4e..5a6e7438fd 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -122,7 +122,15 @@ object TooltipTag { const val PREFS_DEVOPTIONS_DUMPLOGS = "prefs.devoptions.dumplogs" const val PREFS_DEVOPTIONS_LOGSENDER = "prefs.devoptions.logsender" - const val PLUGIN_MANAGER = "plugin.manager" + // Plugin Manager screen + const val PLUGIN_MANAGER_TOOLBAR = "plugin.manager.toolbar" + const val PLUGIN_MANAGER_DOWNLOAD = "plugin.manager.download" + const val PLUGIN_MANAGER_FAB_INSTALL = "plugin.manager.fab.install" + const val PLUGIN_MANAGER_EMPTY_STATE = "plugin.manager.emptystate" + const val PLUGIN_MANAGER_LIST = "plugin.manager.list" + const val PLUGIN_MANAGER_ITEM = "plugin.manager.item" + const val PLUGIN_MANAGER_ITEM_MENU = "plugin.manager.item.menu" + const val TEMPLATE_TABBED_ACTIVITY = "template.tabbed.activity" const val TEMPLATE_LEGACY_PROJECT = "template.legacy.project" const val TEMPLATE_EMPTY_ACTIVITY = "template.empty.activity" From ffa1743aea33a5fa3f666b0e5961770f452fdc28 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 22:58:50 -0700 Subject: [PATCH 10/23] ADFA-5088: Add docdb SQL script for Plugin Manager tooltip tags Adds the Tooltips (7 INSERTs - none of these tags existed before, not even the old shared "plugin.manager" tag) and Content (7 INSERTs, Brotli-compressed HTML) rows the tags added in the preceding commit look up. Co-Authored-By: Claude Sonnet 5 --- .../ADFA-5088-plugin-manager-tooltips.sql | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 docs/docdb/ADFA-5088-plugin-manager-tooltips.sql diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql new file mode 100644 index 0000000000..08185c54b0 --- /dev/null +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -0,0 +1,81 @@ +-- ADFA-5088: Tooltips + Content rows for the Plugin Manager screen. +-- +-- The Plugin Manager screen previously showed one shared tooltip +-- ("plugin.manager") for its toolbar, download icon, FAB, empty state, +-- and every plugin row. The corresponding code change replaces that +-- with a distinct TooltipTag per widget; this script adds the +-- documentation database rows those new tags look up. +-- +-- None of these tags existed before (the old shared "plugin.manager" +-- tag itself had no Tooltips row either), so every row here is a plain +-- INSERT - there are no existing stubs to UPDATE. +-- +-- All rows use categoryId = 1 ("ide"), languageId = 1 ("EN-us"), +-- contentTypeId = 12 ("text/html", brotli-compressed). +-- +-- Apply against the real documentation.db: +-- sqlite3 documentation.db < ADFA-5088-plugin-manager-tooltips.sql +-- +-- The Content section uses `.system echo "" | brotli -Z > /tmp/x.br` +-- immediately before each `INSERT ... READFILE('/tmp/x.br')` so the +-- uncompressed HTML is visible in this script. `.system` and READFILE() +-- require the sqlite3 CLI (not a library binding). If `.system` is +-- disabled in your sqlite3 build, run the `echo ... | brotli -Z > file` +-- line yourself via a shell first, then run just the INSERT statements. + +-- --------------------------------------------------------------------- +-- Tooltips: INSERTs (all new tags) +-- --------------------------------------------------------------------- + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.toolbar', + 'Plugin Manager lists your installed plugins.', + 'Use this screen to install a new plugin, open a plugin''s details, or find more plugins. The back button returns to Preferences.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.download', + 'Find more plugins to install.', + 'Opens a webpage where you can find plugins to add to Code on the Go.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.fab.install', + 'Install a plugin from a file.', + 'Opens a file picker so you can choose a plugin package (a .cgp file) to install.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.emptystate', + 'No plugins installed yet.', + 'This message appears when you have no plugins installed. Use the download icon to find plugins, or the + button to install one from a file.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.list', + 'Your installed plugins.', + 'Each row below shows one installed plugin. Tap a row to see its details, or use its menu button for more actions.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item', + 'Tap for this plugin''s details.', + 'Shows this plugin''s name, status, and version. Tap the row to see full details, including its description and version history.'); + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item.menu', + 'More actions for this plugin.', + 'Opens a menu with actions for this plugin, such as enable, disable, uninstall, or view details. Which actions appear depends on the plugin''s current state.'); + +-- --------------------------------------------------------------------- +-- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. +-- --------------------------------------------------------------------- + +.system echo "

Plugin Manager lists every plugin currently installed in Code on the Go.

From here you can install a new plugin from a file, find more plugins online, and open any installed plugin's details or actions.

" | brotli -Z > /tmp/adfa5088-pm-toolbar.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/toolbar', 1, 12, READFILE('/tmp/adfa5088-pm-toolbar.br')); + +.system echo "

The download icon opens a webpage where you can find plugins to add to Code on the Go.

This opens in your browser or an in-app web view, outside Code on the Go itself.

" | brotli -Z > /tmp/adfa5088-pm-download.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/download', 1, 12, READFILE('/tmp/adfa5088-pm-download.br')); + +.system echo "

The + button installs a plugin you already have as a file.

Tap it to open a file picker, choose a plugin package (a .cgp file), and confirm the install. This does not download anything - use the download icon first if you need to find a plugin file.

" | brotli -Z > /tmp/adfa5088-pm-fab-install.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/fab/install', 1, 12, READFILE('/tmp/adfa5088-pm-fab-install.br')); + +.system echo "

This message appears when Plugin Manager has no plugins to show.

Use the download icon at the top of the screen to find plugins, or the + button to install a plugin file you already have.

" | brotli -Z > /tmp/adfa5088-pm-emptystate.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/emptystate', 1, 12, READFILE('/tmp/adfa5088-pm-emptystate.br')); + +.system echo "

This list shows every plugin installed in Code on the Go, one row per plugin.

Each row shows the plugin's name, version, and current status. Tap a row for its details, or use its menu button for more actions.

" | brotli -Z > /tmp/adfa5088-pm-list.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/list', 1, 12, READFILE('/tmp/adfa5088-pm-list.br')); + +.system echo "

This row represents one installed plugin.

It shows the plugin's name, version, and whether it is enabled, disabled, or failed to load. Tap the row to open its full details.

" | brotli -Z > /tmp/adfa5088-pm-item.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item', 1, 12, READFILE('/tmp/adfa5088-pm-item.br')); + +.system echo "

This button opens a menu of actions for this plugin.

Depending on the plugin's current state, the menu can include enabling it, disabling it, uninstalling it, or viewing its details.

" | brotli -Z > /tmp/adfa5088-pm-item-menu.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-item-menu.br')); From bac9781acb9ab7c9a38794ff7f0894510dc3407a Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 23:54:07 -0700 Subject: [PATCH 11/23] ADFA-5088: Add unit tests for collectTooltipTags Widen collectTooltipTags to internal so a pure-JVM test can call it, and cover the three behaviors that matter: flat key-to-tag mapping, recursing into nested categories but not into nested screens (their children belong to a separate fragment instance), and preserving an empty tooltipTag rather than dropping the key. Co-Authored-By: Claude Sonnet 5 --- .../fragments/IDEPreferencesFragment.kt | 2 +- .../fragments/IDEPreferencesFragmentTest.kt | 99 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt index 06c7c7dfb9..ef0164407d 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt @@ -89,7 +89,7 @@ class IDEPreferencesFragment : BasePreferenceFragment() { } } - private fun collectTooltipTags(children: List): Map { + internal fun collectTooltipTags(children: List): Map { val map = mutableMapOf() fun visit(items: List) { diff --git a/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt new file mode 100644 index 0000000000..8474681ace --- /dev/null +++ b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt @@ -0,0 +1,99 @@ +package com.itsaky.androidide.fragments + +import android.content.Context +import androidx.preference.Preference +import com.google.common.truth.Truth.assertThat +import com.itsaky.androidide.preferences.IPreference +import com.itsaky.androidide.preferences.IPreferenceGroup +import com.itsaky.androidide.preferences.IPreferenceScreen +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class IDEPreferencesFragmentTest { + @Test + fun `collectTooltipTags maps every leaf key to its tooltipTag`() { + val children = + listOf( + FakeItem(key = "a", tooltipTag = "tag.a"), + FakeItem(key = "b", tooltipTag = "tag.b"), + ) + + val tags = IDEPreferencesFragment().collectTooltipTags(children) + + assertThat(tags).containsExactly("a", "tag.a", "b", "tag.b") + } + + @Test + fun `collectTooltipTags recurses into nested categories but not into nested screens`() { + val children = + listOf( + FakeCategory( + key = "category", + tooltipTag = "tag.category", + children = listOf(FakeItem(key = "nested", tooltipTag = "tag.nested")), + ), + FakeScreen( + key = "screen", + tooltipTag = "tag.screen", + // A screen's own children belong to a different fragment instance and must not + // be pulled into this screen's map. + children = listOf(FakeItem(key = "hidden", tooltipTag = "tag.hidden")), + ), + ) + + val tags = IDEPreferencesFragment().collectTooltipTags(children) + + assertThat(tags).containsExactly( + "category", + "tag.category", + "nested", + "tag.nested", + "screen", + "tag.screen", + ) + } + + @Test + fun `collectTooltipTags preserves an empty tooltipTag rather than omitting the key`() { + val children = listOf(FakeItem(key = "untagged", tooltipTag = "")) + + val tags = IDEPreferencesFragment().collectTooltipTags(children) + + assertThat(tags).containsExactly("untagged", "") + } +} + +@Parcelize +private class FakeItem( + override val key: String, + override val tooltipTag: String = "", +) : IPreference() { + @IgnoredOnParcel + override val title: Int = 0 + + override fun onCreateView(context: Context): Preference = throw UnsupportedOperationException() +} + +@Parcelize +private class FakeCategory( + override val key: String, + override val tooltipTag: String = "", + override val children: List = mutableListOf(), +) : IPreferenceGroup() { + @IgnoredOnParcel + override val title: Int = 0 +} + +@Parcelize +private class FakeScreen( + override val key: String, + override val tooltipTag: String = "", + override val children: List = mutableListOf(), +) : IPreferenceScreen() { + @IgnoredOnParcel + override val title: Int = 0 +} From 781dd0559a512c1a4ea5d58740ef2ac5f5a3a3b8 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 23:54:31 -0700 Subject: [PATCH 12/23] ADFA-5088: Fix review findings in the preference tooltips SQL script - Convert every Tooltips and Content statement to an idempotent INSERT ... ON CONFLICT DO UPDATE, so the script is safe to re-run. - Drop the 5 statements for prefs.general, prefs.editor, prefs.editor.xml, prefs.termux, and prefs.git: the local documentation.db this script was authored and validated against turned out to be a stale, gitignored, downloaded copy. The real current database already has good, curated content for these 5 tags (reused as-is by the code for the corresponding screen row) - the removed statements would have silently overwritten it. Re-validated the whole script end to end against a scratch copy of the real current database. - Fix a grammar error in the termux.loglevel detail text ("the terminal own internal logging level" -> "the terminal's own internal logging level"). - Wrap the --info flag's Content HTML in

tags for consistency with every sibling row. Co-Authored-By: Claude Sonnet 5 --- docs/docdb/ADFA-5088-preference-tooltips.sql | 586 ++++++++----------- 1 file changed, 237 insertions(+), 349 deletions(-) diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql index 30198d59f1..ee99e347b2 100644 --- a/docs/docdb/ADFA-5088-preference-tooltips.sql +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -6,17 +6,24 @@ -- (Tier 1 `summary` + Tier 2 `detail`) and a matching Content row (Tier 3 -- HTML page) at a new, item-granular `i/prefs/...` path. -- --- Some `prefs.*` tags already exist in Tooltips as empty stub rows --- (summary = '', detail = ''); those are UPDATEd in place, since --- Tooltips.(categoryId, tag) is UNIQUE and a second INSERT would violate it. --- All other tags are brand new and get a plain INSERT. Content rows are --- always new INSERTs -- no per-item Content rows existed before this script. +-- Deliberately NOT included: prefs.general, prefs.editor, prefs.editor.xml, +-- prefs.termux, and prefs.git already exist in the real documentation.db +-- with good, real (non-empty) summary/detail text -- these tags are reused +-- as-is for the corresponding screen row, so this script leaves them alone +-- rather than overwriting curated production content with a draft. +-- +-- Both Tooltips (UNIQUE(categoryId, tag)) and Content (UNIQUE(path)) are +-- idempotent `INSERT ... ON CONFLICT ... DO UPDATE` upserts below, so the +-- whole script is safe to re-run. -- -- All rows use categoryId = 1 ("ide"), languageId = 1 ("EN-us"), -- contentTypeId = 12 ("text/html", brotli-compressed). -- -- Apply against the real documentation.db: -- sqlite3 documentation.db < ADFA-5088-preference-tooltips.sql +-- The whole script runs inside one transaction (BEGIN/COMMIT below), so a +-- failure partway through leaves the database untouched rather than +-- half-applied. -- -- The Content section uses `.system echo "" | brotli -Z > /tmp/x.br` -- immediately before each `INSERT ... READFILE('/tmp/x.br')` so the @@ -25,313 +32,192 @@ -- in your sqlite3 build, run the `echo ... | brotli -Z > file` line yourself -- via a shell first, then run just the INSERT statements. +BEGIN; + -- --------------------------------------------------------------------- --- Tooltips: UPDATEs (existing empty stub rows) +-- Tooltips: idempotent upserts (existing empty stub rows + brand new tags, +-- all in one form so the script can be re-run safely) -- --------------------------------------------------------------------- -UPDATE Tooltips SET - summary = 'Change general settings for the IDE.', - detail = 'Set the app theme, language, and how Code on the Go opens projects. These settings apply across the whole app.' -WHERE tag = 'prefs.general' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Choose light mode, dark mode, or match your device''s system setting.', - detail = 'This setting controls the color theme of the app. Pick Light, Dark, or Follow system. Follow system switches automatically when your device''s theme changes.' -WHERE tag = 'prefs.general.uimode' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Choose the display language for Code on the Go.', - detail = 'This setting changes the language of the app menus and text. Pick System Default to use your device language, or choose a specific language from the list.' -WHERE tag = 'prefs.general.language' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Reopen your last project automatically when the app starts.', - detail = 'When on, Code on the Go opens the last project you worked on as soon as it starts. When off, the app shows the project list instead.' -WHERE tag = 'prefs.general.openlast' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Ask for confirmation before opening your last project.', - detail = 'When on, Code on the Go shows a confirmation prompt before it reopens your last project. This gives you a chance to go to the project list instead.' -WHERE tag = 'prefs.general.confirmopen' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Change how the code editor looks and behaves.', - detail = 'Set font size, tab size, whitespace display, and other editor behavior. XML-specific formatting has its own sub-screen.' -WHERE tag = 'prefs.editor' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Set the editor text size.', - detail = 'This changes the size of code text in the editor, measured in sp (scale-independent pixels). Use a slider to pick a value between 6 and 32.' -WHERE tag = 'prefs.editor.fontsize' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Set how many spaces one tab indents.', - detail = 'This sets the number of spaces the editor uses for each indent level. Choose from 2, 4, 6, or 8 spaces.' -WHERE tag = 'prefs.editor.tabsize' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Choose which whitespace and line-break marks the editor shows.', - detail = 'This opens a dialog with checkboxes for leading, trailing, inner, and empty-line whitespace, plus line-break marks. Turn on any you want the editor to display.' -WHERE tag = 'prefs.editor.nonprinting' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Insert spaces instead of a tab character when you press Tab.', - detail = 'When on, pressing the Tab key inserts spaces. When off, it inserts a tab character.' -WHERE tag = 'prefs.editor.softtab' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Break long lines so they fit on the screen.', - detail = 'When on, the editor wraps long lines onto multiple visual lines instead of scrolling sideways. This does not change the file content.' -WHERE tag = 'prefs.editor.wordwrap' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Show a magnified view of text while you select it.', - detail = 'When on, pressing and holding on text shows a zoomed-in view of the area around your finger. This makes it easier to place the cursor precisely.' -WHERE tag = 'prefs.editor.magnifier' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Use spaces to define word boundaries when you double-tap to select.', - detail = 'When on, double-tapping a word selects text up to the nearest space. When off, the editor also uses punctuation to decide where a word ends.' -WHERE tag = 'prefs.editor.wordboundaries' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Match code suggestions even when letter case differs.', - detail = 'When on, autocomplete suggestions match class and member names regardless of whether you type upper or lower case letters.' -WHERE tag = 'prefs.editor.matchcase' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Delete a whole blank line with one backspace.', - detail = 'When on, pressing backspace on a line with no visible text removes the entire line at once.' -WHERE tag = 'prefs.editor.deletelines' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Delete a full indent level with one backspace.', - detail = 'When on, pressing backspace at the start of an indented line removes the whole indent level at once, instead of one character at a time.' -WHERE tag = 'prefs.editor.smartbackspace' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Keep the current code block header visible while scrolling.', - detail = 'When on, the editor pins the header line of the current class or method at the top of the screen while you scroll through its body.' -WHERE tag = 'prefs.editor.stickyscroll' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Keep line numbers visible when scrolling sideways.', - detail = 'When on, line numbers stay in place on the left side of the screen even when you scroll a long line horizontally.' -WHERE tag = 'prefs.editor.pinlines' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Format Java code using Google''s style rules.', - detail = 'When on, the code formatter applies Google''s Java style conventions, such as its indentation and spacing rules, instead of the default style.' -WHERE tag = 'prefs.editor.googlestyle' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Open XML-specific formatting settings.', - detail = 'This opens a sub-screen with formatting options that apply only to XML files, such as attribute placement and line width.' -WHERE tag = 'prefs.editor.xml' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Change settings for building and running your app.', - detail = 'Set additional Gradle flags and control whether the app launches automatically after a run installs it.' -WHERE tag = 'prefs.buildrun' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Launch the app automatically after a successful run installs it.', - detail = 'When on, Code on the Go opens your app right after installing it, with no extra confirmation step.' -WHERE tag = 'prefs.buildrun.autolaunch' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Choose extra Gradle flags to add to every build.', - detail = 'This opens a dialog with checkboxes for common Gradle command-line flags, such as --info or --offline. Any flag you turn on is added to every Gradle task the IDE runs.' -WHERE tag = 'prefs.buildrun.flags' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Change settings for the built-in terminal.', - detail = 'Set the terminal logging level, keyboard behavior, and screen margin.' -WHERE tag = 'prefs.termux' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Set how much detail the terminal writes to its internal log.', - detail = 'This controls the terminal own internal logging level, used for troubleshooting the terminal itself. Higher levels record more detail.' -WHERE tag = 'prefs.termux.loglevel' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Log every key you press in the terminal, for debugging.', - detail = 'When on, the terminal records each key press to the system log. This is very verbose and can slow the app down, so leave it off unless you are debugging a keyboard issue.' -WHERE tag = 'prefs.termux.keylogging' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Adjust the terminal margin so the on-screen keyboard does not cover it.', - detail = 'When on, the terminal adjusts its margin to avoid being covered by the on-screen keyboard. If you notice screen flickering, turn this off.' -WHERE tag = 'prefs.termux.margin' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Show the on-screen keyboard only when no physical keyboard is connected.', - detail = 'When on, the terminal hides its on-screen keyboard while a hardware keyboard is connected, and shows it again once the hardware keyboard is disconnected.' -WHERE tag = 'prefs.termux.nohardkeyboard' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Show the on-screen keyboard in the terminal.', - detail = 'When on, the terminal shows its own on-screen keyboard for typing commands. This is on by default.' -WHERE tag = 'prefs.termux.softkeyboard' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Put a tag closing bracket on its own new line.', - detail = 'When on, the formatter places the final ">" or "/>" on a new line after the last attribute. This is off by default.' -WHERE tag = 'prefs.xml.closebracket' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Choose how the formatter writes empty XML elements.', - detail = 'Pick Expand to write empty elements as an open and a close tag, Collapse to write them as a single self-closing tag, or Ignore to leave them as they are.' -WHERE tag = 'prefs.xml.emptyelements' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Set the maximum characters allowed on one line.', - detail = 'This sets the line-width limit, in characters, before the formatter wraps a line onto more than one line. The default is 80.' -WHERE tag = 'prefs.xml.maxlinewidth' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Keep existing line breaks between attributes.', - detail = 'When on, the formatter leaves attributes on the lines you already put them on, instead of joining them onto one line. This is on by default.' -WHERE tag = 'prefs.xml.preserveattributes' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Set how many blank lines to keep between elements.', - detail = 'This sets the maximum number of blank lines the formatter keeps between elements when it reformats a file. Extra blank lines beyond this number are removed. The default is 2.' -WHERE tag = 'prefs.xml.preservenewlines' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Add a space before a tag self-closing slash.', - detail = 'When on, the formatter writes a space before "/>", producing "" instead of "". This is on by default.' -WHERE tag = 'prefs.xml.spacebeforeclose' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Set the indent size for attributes on split lines.', - detail = 'This sets how many spaces the formatter uses to indent an attribute placed on its own line. By default it is half the editor tab size.' -WHERE tag = 'prefs.xml.splitattribindent' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Remove extra spaces at the end of lines.', - detail = 'When on, the formatter deletes any whitespace left at the end of each line. This is on by default.' -WHERE tag = 'prefs.xml.trimwhitespace' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Change experimental and debugging settings.', - detail = 'These settings are for troubleshooting Code on the Go itself, not for normal project configuration. Only change them if you are diagnosing a problem.' -WHERE tag = 'prefs.devoptions' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Save the IDE internal logs to a file.', - detail = 'When on, Code on the Go writes its internal logs to a file at $HOME/.cg/logs, for troubleshooting.' -WHERE tag = 'prefs.devoptions.dumplogs' AND categoryId = 1; - -UPDATE Tooltips SET - summary = 'Show logs from your running app inside Code on the Go.', - detail = 'When on, Code on the Go displays log output from apps you run. Turn this off to stop showing those logs.' -WHERE tag = 'prefs.devoptions.logsender' AND categoryId = 1; +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.uimode', 'Choose light mode, dark mode, or match your device''s system setting.', 'This setting controls the color theme of the app. Pick Light, Dark, or Follow system. Follow system switches automatically when your device''s theme changes.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; --- --------------------------------------------------------------------- --- Tooltips: INSERTs (brand new tags) --- --------------------------------------------------------------------- +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.language', 'Choose the display language for Code on the Go.', 'This setting changes the language of the app menus and text. Pick System Default to use your device language, or choose a specific language from the list.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.openlast', 'Reopen your last project automatically when the app starts.', 'When on, Code on the Go opens the last project you worked on as soon as it starts. When off, the app shows the project list instead.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.confirmopen', 'Ask for confirmation before opening your last project.', 'When on, Code on the Go shows a confirmation prompt before it reopens your last project. This gives you a chance to go to the project list instead.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.fontsize', 'Set the editor text size.', 'This changes the size of code text in the editor, measured in sp (scale-independent pixels). Use a slider to pick a value between 6 and 32.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.tabsize', 'Set how many spaces one tab indents.', 'This sets the number of spaces the editor uses for each indent level. Choose from 2, 4, 6, or 8 spaces.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting', 'Choose which whitespace and line-break marks the editor shows.', 'This opens a dialog with checkboxes for leading, trailing, inner, and empty-line whitespace, plus line-break marks. Turn on any you want the editor to display.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.softtab', 'Insert spaces instead of a tab character when you press Tab.', 'When on, pressing the Tab key inserts spaces. When off, it inserts a tab character.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.wordwrap', 'Break long lines so they fit on the screen.', 'When on, the editor wraps long lines onto multiple visual lines instead of scrolling sideways. This does not change the file content.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.magnifier', 'Show a magnified view of text while you select it.', 'When on, pressing and holding on text shows a zoomed-in view of the area around your finger. This makes it easier to place the cursor precisely.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.wordboundaries', 'Use spaces to define word boundaries when you double-tap to select.', 'When on, double-tapping a word selects text up to the nearest space. When off, the editor also uses punctuation to decide where a word ends.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.matchcase', 'Match code suggestions even when letter case differs.', 'When on, autocomplete suggestions match class and member names regardless of whether you type upper or lower case letters.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.deletelines', 'Delete a whole blank line with one backspace.', 'When on, pressing backspace on a line with no visible text removes the entire line at once.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.smartbackspace', 'Delete a full indent level with one backspace.', 'When on, pressing backspace at the start of an indented line removes the whole indent level at once, instead of one character at a time.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.stickyscroll', 'Keep the current code block header visible while scrolling.', 'When on, the editor pins the header line of the current class or method at the top of the screen while you scroll through its body.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.pinlines', 'Keep line numbers visible when scrolling sideways.', 'When on, line numbers stay in place on the left side of the screen even when you scroll a long line horizontally.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.googlestyle', 'Format Java code using Google''s style rules.', 'When on, the code formatter applies Google''s Java style conventions, such as its indentation and spacing rules, instead of the default style.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.leading', - 'Show whitespace at the start of each line.', - 'When on, the editor marks spaces and tabs that come before the first visible character on a line.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun', 'Change settings for building and running your app.', 'Set additional Gradle flags and control whether the app launches automatically after a run installs it.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.trailing', - 'Show whitespace at the end of each line.', - 'When on, the editor marks spaces and tabs that come after the last visible character on a line.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.autolaunch', 'Launch the app automatically after a successful run installs it.', 'When on, Code on the Go opens your app right after installing it, with no extra confirmation step.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.inner', - 'Show whitespace between words.', - 'When on, the editor marks spaces and tabs that appear between two visible characters on a line.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags', 'Choose extra Gradle flags to add to every build.', 'This opens a dialog with checkboxes for common Gradle command-line flags, such as --info or --offline. Any flag you turn on is added to every Gradle task the IDE runs.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.emptylines', - 'Show whitespace on lines with no visible text.', - 'When on, the editor marks spaces and tabs on lines that contain only whitespace.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.loglevel', 'Set how much detail the terminal writes to its internal log.', 'This controls the terminal''s own internal logging level, used for troubleshooting the terminal itself. Higher levels record more detail.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.linebreaks', - 'Show a mark at the end of each line.', - 'When on, the editor draws a small symbol where each line ends, so line breaks are visible.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.keylogging', 'Log every key you press in the terminal, for debugging.', 'When on, the terminal records each key press to the system log. This is very verbose and can slow the app down, so leave it off unless you are debugging a keyboard issue.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.trimfinalnewline', - 'Remove blank lines at the end of an XML file.', - 'When on, the formatter deletes empty lines at the end of the file, leaving no trailing blank lines. This is off by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.margin', 'Adjust the terminal margin so the on-screen keyboard does not cover it.', 'When on, the terminal adjusts its margin to avoid being covered by the on-screen keyboard. If you notice screen flickering, turn this off.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.insertfinalnewline', - 'Add one newline at the end of an XML file.', - 'When on, the formatter makes sure the file ends with exactly one newline character. This is on by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.nohardkeyboard', 'Show the on-screen keyboard only when no physical keyboard is connected.', 'When on, the terminal hides its on-screen keyboard while a hardware keyboard is connected, and shows it again once the hardware keyboard is disconnected.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.splitattributes', - 'Put each XML attribute on its own line.', - 'When on, the formatter places every attribute of a tag on a separate line instead of one line. This is on by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.softkeyboard', 'Show the on-screen keyboard in the terminal.', 'When on, the terminal shows its own on-screen keyboard for typing commands. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincdatalines', - 'Combine multiple CDATA lines into one.', - 'When on, the formatter merges the lines of a multi-line CDATA section into a single line. This is off by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.closebracket', 'Put a tag closing bracket on its own new line.', 'When on, the formatter places the final ">" or "/>" on a new line after the last attribute. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincommentlines', - 'Combine multiple single-line comments into one.', - 'When on, the formatter merges adjacent single-line XML comments into a single line. This is on by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.emptyelements', 'Choose how the formatter writes empty XML elements.', 'Pick Expand to write empty elements as an open and a close tag, Collapse to write them as a single self-closing tag, or Ignore to leave them as they are.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincontentlines', - 'Combine multiple lines of element text into one.', - 'When on, the formatter merges multiple lines of text inside an XML element into a single line. This is off by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.maxlinewidth', 'Set the maximum characters allowed on one line.', 'This sets the line-width limit, in characters, before the formatter wraps a line onto more than one line. The default is 80.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preserveemptycontent', - 'Keep tags that stand alone on an empty line as they are.', - 'When on, the formatter leaves an element alone on its own blank line unchanged, instead of collapsing it. This is on by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preserveattributes', 'Keep existing line breaks between attributes.', 'When on, the formatter leaves attributes on the lines you already put them on, instead of joining them onto one line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.stacktrace', - 'Show a full stack trace when a build fails.', - 'Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure instead of a short summary.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preservenewlines', 'Set how many blank lines to keep between elements.', 'This sets the maximum number of blank lines the formatter keeps between elements when it reformats a file. Extra blank lines beyond this number are removed. The default is 2.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.info', - 'Show detailed log messages for each build task.', - 'Sets the Gradle --info flag. Gradle prints detailed log messages as each task runs, instead of just a summary line for each task.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.spacebeforeclose', 'Add a space before a tag self-closing slash.', 'When on, the formatter writes a space before "/>", producing "" instead of "". This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.debug', - 'Show maximum detail in build logs.', - 'Sets the Gradle --debug flag. Gradle prints very detailed, low-level log messages for every task. This produces a large amount of output and can slow the build down.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.splitattribindent', 'Set the indent size for attributes on split lines.', 'This sets how many spaces the formatter uses to indent an attribute placed on its own line. By default it is half the editor tab size.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.scan', - 'Publish a Gradle Build Scan report link.', - 'Sets the Gradle --scan flag. After the build, Gradle uploads build data and gives you a link to a Build Scan report with detailed information.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.trimwhitespace', 'Remove extra spaces at the end of lines.', 'When on, the formatter deletes any whitespace left at the end of each line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.warningmodeall', - 'Show every deprecation warning during the build.', - 'Sets the Gradle --warning-mode all flag. Gradle lists every individual deprecation warning instead of just a count at the end of the build.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.devoptions', 'Change experimental and debugging settings.', 'These settings are for troubleshooting Code on the Go itself, not for normal project configuration. Only change them if you are diagnosing a problem.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.buildcache', - 'Reuse outputs from a previous build to speed up this one.', - 'Sets the Gradle --build-cache flag. Gradle reuses task outputs from an earlier build when the inputs have not changed, which can make the build faster.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.devoptions.dumplogs', 'Save the IDE internal logs to a file.', 'When on, Code on the Go writes its internal logs to a file at $HOME/.cg/logs, for troubleshooting.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.offline', - 'Build without any network access.', - 'Sets the Gradle --offline flag. Gradle uses only dependencies already downloaded to your device and does not try to reach the network.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.devoptions.logsender', 'Show logs from your running app inside Code on the Go.', 'When on, Code on the Go displays log output from apps you run. Turn this off to stop showing those logs.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.crashreports', - 'Show a notification if the terminal crashes.', - 'When on, Code on the Go shows a notification with a crash report after the terminal process crashes. This is on by default.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.leading', 'Show whitespace at the start of each line.', 'When on, the editor marks spaces and tabs that come before the first visible character on a line.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git', - 'Set your Git author identity.', - 'This screen sets the name and email address recorded as the author of your Git commits.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.trailing', 'Show whitespace at the end of each line.', 'When on, the editor marks spaces and tabs that come after the last visible character on a line.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.username', - 'Set the name used as the author of your commits.', - 'This name is recorded on every Git commit you make from Code on the Go.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.inner', 'Show whitespace between words.', 'When on, the editor marks spaces and tabs that appear between two visible characters on a line.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.useremail', - 'Set the email address used as the author of your commits.', - 'This email address is recorded on every Git commit you make from Code on the Go.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.emptylines', 'Show whitespace on lines with no visible text.', 'When on, the editor marks spaces and tabs on lines that contain only whitespace.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.pluginmanager', - 'Manage IDE plugins and extensions.', - 'This opens Plugin Manager, where you can install, remove, and configure plugins that add features to Code on the Go.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.editor.nonprinting.linebreaks', 'Show a mark at the end of each line.', 'When on, the editor draws a small symbol where each line ends, so line breaks are visible.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about', - 'See app version and other information about Code on the Go.', - 'This opens the About screen, with details such as the app version and credits.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.trimfinalnewline', 'Remove blank lines at the end of an XML file.', 'When on, the formatter deletes empty lines at the end of the file, leaving no trailing blank lines. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.insertfinalnewline', 'Add one newline at the end of an XML file.', 'When on, the formatter makes sure the file ends with exactly one newline character. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.splitattributes', 'Put each XML attribute on its own line.', 'When on, the formatter places every attribute of a tag on a separate line instead of one line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincdatalines', 'Combine multiple CDATA lines into one.', 'When on, the formatter merges the lines of a multi-line CDATA section into a single line. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincommentlines', 'Combine multiple single-line comments into one.', 'When on, the formatter merges adjacent single-line XML comments into a single line. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.joincontentlines', 'Combine multiple lines of element text into one.', 'When on, the formatter merges multiple lines of text inside an XML element into a single line. This is off by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.xml.preserveemptycontent', 'Keep tags that stand alone on an empty line as they are.', 'When on, the formatter leaves an element alone on its own blank line unchanged, instead of collapsing it. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.stacktrace', 'Show a full stack trace when a build fails.', 'Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure instead of a short summary.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.info', 'Show detailed log messages for each build task.', 'Sets the Gradle --info flag. Gradle prints detailed log messages as each task runs, instead of just a summary line for each task.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.debug', 'Show maximum detail in build logs.', 'Sets the Gradle --debug flag. Gradle prints very detailed, low-level log messages for every task. This produces a large amount of output and can slow the build down.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.scan', 'Publish a Gradle Build Scan report link.', 'Sets the Gradle --scan flag. After the build, Gradle uploads build data and gives you a link to a Build Scan report with detailed information.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.warningmodeall', 'Show every deprecation warning during the build.', 'Sets the Gradle --warning-mode all flag. Gradle lists every individual deprecation warning instead of just a count at the end of the build.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.buildcache', 'Reuse outputs from a previous build to speed up this one.', 'Sets the Gradle --build-cache flag. Gradle reuses task outputs from an earlier build when the inputs have not changed, which can make the build faster.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.buildrun.flags.offline', 'Build without any network access.', 'Sets the Gradle --offline flag. Gradle uses only dependencies already downloaded to your device and does not try to reach the network.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.termux.crashreports', 'Show a notification if the terminal crashes.', 'When on, Code on the Go shows a notification with a crash report after the terminal process crashes. This is on by default.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.username', 'Set the name used as the author of your commits.', 'This name is recorded on every Git commit you make from Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.git.useremail', 'Set the email address used as the author of your commits.', 'This email address is recorded on every Git commit you make from Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.pluginmanager', 'Manage IDE plugins and extensions.', 'This opens Plugin Manager, where you can install, remove, and configure plugins that add features to Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; + +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about', 'See app version and other information about Code on the Go.', 'This opens the About screen, with details such as the app version and credits.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -- --------------------------------------------------------------------- -- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. @@ -340,196 +226,198 @@ INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about' -- --------------------------------------------------------------------- .system echo "

The General screen holds settings that are not specific to the editor or to a single project. Use it to set the app theme, choose a display language, and control how Code on the Go opens the last project on launch.

Changes here apply immediately and affect the whole app.

" | brotli -Z > /tmp/adfa5088-prefs-general.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general', 1, 12, READFILE('/tmp/adfa5088-prefs-general.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general', 1, 12, READFILE('/tmp/adfa5088-prefs-general.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

UI mode sets the color theme for Code on the Go.

  • Light: always use light colors.
  • Dark: always use dark colors.
  • Follow system: match your device's current theme, and switch automatically when it changes.
" | brotli -Z > /tmp/adfa5088-prefs-general-uimode.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/uimode', 1, 12, READFILE('/tmp/adfa5088-prefs-general-uimode.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/uimode', 1, 12, READFILE('/tmp/adfa5088-prefs-general-uimode.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Language sets the display language for the Code on the Go interface.

Choose System Default to use your device language setting, or pick one of the supported languages directly. The app restarts the affected screens to apply the change.

" | brotli -Z > /tmp/adfa5088-prefs-general-language.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/language', 1, 12, READFILE('/tmp/adfa5088-prefs-general-language.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/language', 1, 12, READFILE('/tmp/adfa5088-prefs-general-language.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Open last project controls what happens when you start Code on the Go.

Turn it on to skip the project list and go straight into your most recent project. Turn it off to see the project list every time.

" | brotli -Z > /tmp/adfa5088-prefs-general-openlast.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/openlast', 1, 12, READFILE('/tmp/adfa5088-prefs-general-openlast.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/openlast', 1, 12, READFILE('/tmp/adfa5088-prefs-general-openlast.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Confirm project opening adds a confirmation step before Code on the Go reopens your last project automatically.

Use this if you often want to switch to a different project instead of continuing the last one.

" | brotli -Z > /tmp/adfa5088-prefs-general-confirmopen.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/confirmopen', 1, 12, READFILE('/tmp/adfa5088-prefs-general-confirmopen.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/confirmopen', 1, 12, READFILE('/tmp/adfa5088-prefs-general-confirmopen.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

The Editor screen holds settings for the code editor: font size, tab size, word wrap, whitespace display, and other editing behavior.

Formatting options specific to XML files are on a separate sub-screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor', 1, 12, READFILE('/tmp/adfa5088-prefs-editor.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor', 1, 12, READFILE('/tmp/adfa5088-prefs-editor.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Font size sets the text size used in the code editor, in sp units.

Choose a larger value to make code easier to read, or a smaller value to fit more code on the screen. The allowed range is 6 to 32.

" | brotli -Z > /tmp/adfa5088-prefs-editor-fontsize.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/fontsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-fontsize.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/fontsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-fontsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Tab size sets the number of spaces that one tab character represents in the editor.

Pick a value that matches your project code style: 2, 4, 6, or 8 spaces.

" | brotli -Z > /tmp/adfa5088-prefs-editor-tabsize.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/tabsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-tabsize.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/tabsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-tabsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Show non-printing characters controls which invisible characters, such as spaces, tabs, and line breaks, the editor marks visibly.

Open this setting to choose which kinds to show: leading whitespace, trailing whitespace, whitespace between words, whitespace on empty lines, and line-break marks.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Leading marks whitespace characters, such as spaces and tabs, that appear before the first visible character on a line.

Turn this on to see indentation whitespace clearly.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-leading.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/leading', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-leading.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/leading', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-leading.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Trailing marks whitespace characters that appear after the last visible character on a line.

Turn this on to spot stray trailing spaces or tabs.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-trailing.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/trailing', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-trailing.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/trailing', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-trailing.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Inner marks whitespace characters that appear between words or tokens within a line, rather than at the start or end.

Turn this on to check for irregular spacing inside a line.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-inner.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/inner', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-inner.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/inner', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-inner.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Empty lines marks whitespace characters on lines that have no visible text, only spaces or tabs.

Turn this on to spot stray whitespace on otherwise blank lines.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-emptylines.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/emptylines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-emptylines.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/emptylines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-emptylines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Line breaks draws a small marker at the end of each line to show where the line break occurs.

Turn this on to see line endings clearly, for example when comparing files with different line-ending styles.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/linebreaks', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/linebreaks', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Use soft tab controls what the editor inserts when you press the Tab key.

Turn this on to insert spaces instead of a tab character. This can help keep code consistent across editors that render tabs differently.

" | brotli -Z > /tmp/adfa5088-prefs-editor-softtab.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/softtab', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-softtab.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/softtab', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-softtab.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Word wrap breaks long lines of code into multiple visual lines so they fit within the visible width of the editor.

The underlying file is not changed; only the way it is displayed changes.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordwrap.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordwrap', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordwrap.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordwrap', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordwrap.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Enable magnifier shows a zoomed-in view of the text around your finger when you press and hold to select text.

This makes it easier to position the cursor precisely on a small screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor-magnifier.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/magnifier', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-magnifier.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/magnifier', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-magnifier.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Use spaces as word boundaries changes how the editor decides what counts as one word when you double-tap to select text.

When on, only blank space marks the edge of a word. When off, the editor also treats punctuation, such as periods and underscores, as word boundaries.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordboundaries.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordboundaries', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordboundaries.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordboundaries', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordboundaries.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Match completions in lower case controls whether the code editor autocomplete feature is case-sensitive.

When on, typing in lower case still matches suggestions that use upper case letters, such as class names.

" | brotli -Z > /tmp/adfa5088-prefs-editor-matchcase.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/matchcase', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-matchcase.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/matchcase', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-matchcase.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Delete empty lines on backspace changes what happens when you press backspace on a line with no visible text.

When on, the whole empty line is removed in one step, instead of removing one whitespace character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-deletelines.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/deletelines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-deletelines.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/deletelines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-deletelines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Smart backspace indent changes what one backspace press removes when the cursor is inside leading indentation.

When on, it removes a full indent level at once. When off, it removes one character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-smartbackspace.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/smartbackspace', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-smartbackspace.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/smartbackspace', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-smartbackspace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Sticky scroll keeps the header line of the code block you are inside, such as a class or method declaration, visible at the top of the editor while you scroll down through its contents.

This helps you keep track of context in long files.

" | brotli -Z > /tmp/adfa5088-prefs-editor-stickyscroll.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/stickyscroll', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-stickyscroll.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/stickyscroll', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-stickyscroll.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Pin line numbers keeps the line-number column fixed on the left side of the editor.

Without this, scrolling a long line horizontally can move the line numbers out of view along with the code.

" | brotli -Z > /tmp/adfa5088-prefs-editor-pinlines.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/pinlines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-pinlines.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/pinlines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-pinlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Use Google Java Style code formatting applies Google's published conventions for Java source code, such as indentation, spacing, and line-wrapping rules, when you format code.

Turn this on if your project follows Google's Java style guide.

" | brotli -Z > /tmp/adfa5088-prefs-editor-googlestyle.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/googlestyle', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-googlestyle.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/googlestyle', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-googlestyle.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

XML formatting options opens a sub-screen of settings that control how Code on the Go formats XML files, separate from the general editor settings.

" | brotli -Z > /tmp/adfa5088-prefs-editor-xml.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/xml', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-xml.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/xml', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-xml.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Trim final new line removes empty lines at the very end of an XML file when the formatter runs.

This is off by default.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimfinalnewline.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimfinalnewline.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Insert final new line adds a single newline character at the end of an XML file if one is not already there.

Many tools expect files to end this way.

" | brotli -Z > /tmp/adfa5088-prefs-xml-insertfinalnewline.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/insertfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-insertfinalnewline.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/insertfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-insertfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Split attributes places each attribute of an XML tag on its own line when the formatter runs.

This makes tags with many attributes easier to read and to compare in version control.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattributes.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattributes.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Join CDATA lines combines the lines of a CDATA section into a single line when the formatter runs.

CDATA sections hold raw, unescaped text or markup inside an XML file.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincdatalines.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincdatalines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincdatalines.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincdatalines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincdatalines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Join comment lines combines adjacent single-line XML comments into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincommentlines.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincommentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincommentlines.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincommentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincommentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Join content lines combines multiple lines of text inside a single XML element into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincontentlines.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincontentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincontentlines.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincontentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincontentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Space before empty close tag adds one space before the closing /> of a self-closing XML tag, so the formatter produces <foo /> instead of <foo/>.

" | brotli -Z > /tmp/adfa5088-prefs-xml-spacebeforeclose.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/spacebeforeclose', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-spacebeforeclose.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/spacebeforeclose', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-spacebeforeclose.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Preserve empty content keeps an XML element that appears alone on an otherwise empty line as it is, instead of collapsing or moving it during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveemptycontent.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveemptycontent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveemptycontent.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveemptycontent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveemptycontent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Preserve attribute line breaks keeps attributes on the separate lines you already placed them on, instead of collapsing them onto a single line during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveattributes.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveattributes.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Closing bracket on new line places the final closing bracket of a tag, including self-closing tags, on its own new line after the last attribute.

" | brotli -Z > /tmp/adfa5088-prefs-xml-closebracket.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/closebracket', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-closebracket.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/closebracket', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-closebracket.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Trim trailing whitespace removes any spaces or tabs left at the end of each line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimwhitespace.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimwhitespace', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimwhitespace.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimwhitespace', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimwhitespace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Maximum line width sets the number of characters allowed on a single line before the formatter wraps it onto additional lines.

The default value is 80 characters.

" | brotli -Z > /tmp/adfa5088-prefs-xml-maxlinewidth.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/maxlinewidth', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-maxlinewidth.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/maxlinewidth', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-maxlinewidth.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Preserve new lines sets the maximum number of consecutive blank lines the formatter keeps between XML elements.

Any blank lines beyond this limit are removed. The default is 2.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preservenewlines.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preservenewlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preservenewlines.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preservenewlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preservenewlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Split attributes indent size sets the number of spaces used to indent an attribute placed on its own line by the formatter.

By default, this is half the editor's tab size.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattribindent.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattribindent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattribindent.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattribindent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattribindent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Empty elements behavior controls how the formatter writes an XML element that has no content.

  • Expand: write a separate open and close tag, for example <foo></foo>.
  • Collapse: write a single self-closing tag, for example <foo/>.
  • Ignore: leave the element as it is in the source file.

The default is Collapse.

" | brotli -Z > /tmp/adfa5088-prefs-xml-emptyelements.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/emptyelements', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-emptyelements.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/emptyelements', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-emptyelements.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

The Build & Run screen holds settings for the Gradle build and for running your app: additional Gradle command-line flags, and whether to launch the app automatically after installation.

" | brotli -Z > /tmp/adfa5088-prefs-build.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build', 1, 12, READFILE('/tmp/adfa5088-prefs-build.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build', 1, 12, READFILE('/tmp/adfa5088-prefs-build.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Launch app after installation controls whether Code on the Go opens your app automatically once a run finishes installing it on the device.

When off, you need to launch the app yourself.

" | brotli -Z > /tmp/adfa5088-prefs-build-autolaunch.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/autolaunch', 1, 12, READFILE('/tmp/adfa5088-prefs-build-autolaunch.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/autolaunch', 1, 12, READFILE('/tmp/adfa5088-prefs-build-autolaunch.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Additional Gradle flags lets you turn on extra command-line flags that Code on the Go adds to every Gradle task it runs, such as build and sync.

Use this to get more detailed logs, use a build cache, or work offline, without typing the flags yourself each time.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure, which can help diagnose the exact cause.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-stacktrace.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--stacktrace', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-stacktrace.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--stacktrace', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-stacktrace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; -.system echo "Sets the Gradle --info flag to produce more detailed log messages as each task runs, rather than just a summary line for each task." | brotli -Z > /tmp/adfa5088-prefs-build-flags-info.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--info', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-info.br')); +.system echo "

Sets the Gradle --info flag to produce more detailed log messages as each task runs, rather than just a summary line for each task.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-info.br +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--info', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-info.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Sets the Gradle --debug flag. Gradle prints its most detailed log level for every task.

This produces a lot of output and can make the build noticeably slower. Use it only when you need to diagnose a specific problem.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-debug.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--debug', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-debug.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--debug', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-debug.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Sets the Gradle --scan flag. After the build finishes, Gradle uploads data about the build and gives you a link to a Build Scan report.

The report shows detailed information about tasks, dependencies, and timing. This requires network access.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-scan.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--scan', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-scan.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--scan', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-scan.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Sets the Gradle --warning-mode all flag. Gradle prints every individual deprecation warning during the build, instead of only a summary count at the end.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-warningmodeall.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--warning-mode-all', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-warningmodeall.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--warning-mode-all', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-warningmodeall.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Sets the Gradle --build-cache flag. Gradle reuses outputs from a previous build for any task whose inputs have not changed, instead of running that task again.

This can noticeably speed up repeated builds.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-buildcache.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--build-cache', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-buildcache.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--build-cache', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-buildcache.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Sets the Gradle --offline flag. Gradle uses only the dependencies already downloaded to your device and does not attempt any network access.

The build fails if a required dependency has not been downloaded yet.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-offline.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--offline', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-offline.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--offline', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-offline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

The Terminal screen holds settings for the built-in terminal emulator: internal logging, keyboard behavior, crash notifications, and screen margin.

" | brotli -Z > /tmp/adfa5088-prefs-termux.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux', 1, 12, READFILE('/tmp/adfa5088-prefs-termux.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux', 1, 12, READFILE('/tmp/adfa5088-prefs-termux.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Log Level sets how much detail the terminal emulator writes to its own internal log, separate from your app logs.

Use a higher level only when troubleshooting a terminal problem, since it produces more log output.

" | brotli -Z > /tmp/adfa5088-prefs-termux-loglevel.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/loglevel', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-loglevel.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/loglevel', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-loglevel.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Terminal View Key Logging records each key you press in the terminal to the system log.

This produces a large amount of log output and can cause performance problems, so it is off by default. Turn it on only to debug a keyboard-related issue.

" | brotli -Z > /tmp/adfa5088-prefs-termux-keylogging.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/keylogging', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-keylogging.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/keylogging', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-keylogging.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Crash Report Notifications controls whether Code on the Go shows a notification after the terminal process crashes.

The notification lets you view or share a report about the crash. This is on by default.

" | brotli -Z > /tmp/adfa5088-prefs-termux-crashreports.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/crashreports', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-crashreports.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/crashreports', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-crashreports.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Soft Keyboard Enabled turns on the terminal's own on-screen keyboard, which includes keys not found on a standard keyboard, such as Ctrl and Esc.

" | brotli -Z > /tmp/adfa5088-prefs-termux-softkeyboard.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/softkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-softkeyboard.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/softkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-softkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Soft Keyboard Only If No Hardware shows the on-screen keyboard only when no physical keyboard is connected to your device.

This is off by default, meaning the on-screen keyboard can show even with a hardware keyboard connected.

" | brotli -Z > /tmp/adfa5088-prefs-termux-nohardkeyboard.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/nohardkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-nohardkeyboard.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/nohardkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-nohardkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Terminal Margin Adjustment changes the terminal view margin to try to prevent the on-screen keyboard from covering part of the terminal or its extra keys row.

This is on by default. If it causes screen flickering on your device, turn it off.

" | brotli -Z > /tmp/adfa5088-prefs-termux-margin.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/margin', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-margin.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/margin', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-margin.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

The Git screen sets your author identity for Git: the name and email address recorded on every commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git', 1, 12, READFILE('/tmp/adfa5088-prefs-git.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git', 1, 12, READFILE('/tmp/adfa5088-prefs-git.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

User name sets the name recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-username.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/username', 1, 12, READFILE('/tmp/adfa5088-prefs-git-username.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/username', 1, 12, READFILE('/tmp/adfa5088-prefs-git-username.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

User email sets the email address recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-useremail.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/useremail', 1, 12, READFILE('/tmp/adfa5088-prefs-git-useremail.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/useremail', 1, 12, READFILE('/tmp/adfa5088-prefs-git-useremail.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Plugin Manager opens a screen where you can browse, install, remove, and configure plugins that extend Code on the Go with extra features.

" | brotli -Z > /tmp/adfa5088-prefs-pluginmanager.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/pluginmanager', 1, 12, READFILE('/tmp/adfa5088-prefs-pluginmanager.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/pluginmanager', 1, 12, READFILE('/tmp/adfa5088-prefs-pluginmanager.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

About Code on the Go opens a screen with details about the app, such as its version number and credits.

" | brotli -Z > /tmp/adfa5088-prefs-about.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/about', 1, 12, READFILE('/tmp/adfa5088-prefs-about.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/about', 1, 12, READFILE('/tmp/adfa5088-prefs-about.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Developer Options holds experimental and debugging settings for Code on the Go, such as log dumping and log sending controls.

These settings are meant for troubleshooting, not everyday use.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Dump logs writes the Code on the Go internal logs to a file at ~/.cg/logs, inside your home directory.

Turn this on when you need to collect logs to diagnose a problem.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-dumplogs.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/dumplogs', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-dumplogs.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/dumplogs', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-dumplogs.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

Enable LogSender controls whether Code on the Go shows log output from the apps you run, inside its own log viewer.

This is on by default. Turn it off if you do not want to see app logs inside the IDE.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-logsender.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-logsender.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-logsender.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +COMMIT; From 4ab13fdfce4dbaabc2b8f35c57ef8c14e979da79 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Wed, 12 Aug 2026 23:54:48 -0700 Subject: [PATCH 13/23] ADFA-5088: Idempotent upserts and dead-tag cleanup in the Plugin Manager SQL - Convert every Tooltips and Content statement to an idempotent INSERT ... ON CONFLICT DO UPDATE, matching the preference tooltips script, so re-running is safe. - Delete the now-dead "plugin.manager" tag and its one TooltipButtons row: no code references that string any more now that every widget has its own tag. The Content page its "Learn more" button linked to (i/plugin-install.html) is left in place - it isn't clearly unreachable the way the Tooltips/TooltipButtons rows are. Validated end to end (apply, re-run, and the delete-then-noop path) against a scratch copy of the real current documentation.db. Co-Authored-By: Claude Sonnet 5 --- .../ADFA-5088-plugin-manager-tooltips.sql | 82 +++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql index 08185c54b0..8184cde159 100644 --- a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -6,15 +6,25 @@ -- with a distinct TooltipTag per widget; this script adds the -- documentation database rows those new tags look up. -- --- None of these tags existed before (the old shared "plugin.manager" --- tag itself had no Tooltips row either), so every row here is a plain --- INSERT - there are no existing stubs to UPDATE. +-- None of the new tags below existed before. The old shared "plugin.manager" +-- tag (a different string from every tag below) is now dead - no code +-- references it any more - so this script deletes it and its one +-- TooltipButtons row, but leaves the Content page that button linked to +-- (i/plugin-install.html) in place, since Content isn't clearly unreachable +-- the way the Tooltips/TooltipButtons rows are. +-- +-- Both Tooltips (UNIQUE(categoryId, tag)) and Content (UNIQUE(path)) are +-- idempotent `INSERT ... ON CONFLICT ... DO UPDATE` upserts below, so the +-- whole script is safe to re-run. -- -- All rows use categoryId = 1 ("ide"), languageId = 1 ("EN-us"), -- contentTypeId = 12 ("text/html", brotli-compressed). -- -- Apply against the real documentation.db: -- sqlite3 documentation.db < ADFA-5088-plugin-manager-tooltips.sql +-- The whole script runs inside one transaction (BEGIN/COMMIT below), so a +-- failure partway through leaves the database untouched rather than +-- half-applied. -- -- The Content section uses `.system echo "" | brotli -Z > /tmp/x.br` -- immediately before each `INSERT ... READFILE('/tmp/x.br')` so the @@ -23,59 +33,67 @@ -- disabled in your sqlite3 build, run the `echo ... | brotli -Z > file` -- line yourself via a shell first, then run just the INSERT statements. +BEGIN; + +-- --------------------------------------------------------------------- +-- Remove the dead "plugin.manager" tag: no code path can reach it any +-- more now that every widget has its own tag. Delete the TooltipButtons +-- row first (it references Tooltips.id via a foreign key). +-- --------------------------------------------------------------------- + +DELETE FROM TooltipButtons +WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager' AND categoryId = 1); + +DELETE FROM Tooltips WHERE tag = 'plugin.manager' AND categoryId = 1; + -- --------------------------------------------------------------------- --- Tooltips: INSERTs (all new tags) +-- Tooltips: idempotent upserts (all new tags) -- --------------------------------------------------------------------- -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.toolbar', - 'Plugin Manager lists your installed plugins.', - 'Use this screen to install a new plugin, open a plugin''s details, or find more plugins. The back button returns to Preferences.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.toolbar', 'Plugin Manager lists your installed plugins.', 'Use this screen to install a new plugin, open a plugin''s details, or find more plugins. The back button returns to Preferences.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.download', - 'Find more plugins to install.', - 'Opens a webpage where you can find plugins to add to Code on the Go.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.download', 'Find more plugins to install.', 'Opens a webpage where you can find plugins to add to Code on the Go.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.fab.install', - 'Install a plugin from a file.', - 'Opens a file picker so you can choose a plugin package (a .cgp file) to install.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.fab.install', 'Install a plugin from a file.', 'Opens a file picker so you can choose a plugin package (a .cgp file) to install.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.emptystate', - 'No plugins installed yet.', - 'This message appears when you have no plugins installed. Use the download icon to find plugins, or the + button to install one from a file.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.emptystate', 'No plugins installed yet.', 'This message appears when you have no plugins installed. Use the download icon to find plugins, or the + button to install one from a file.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.list', - 'Your installed plugins.', - 'Each row below shows one installed plugin. Tap a row to see its details, or use its menu button for more actions.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.list', 'Your installed plugins.', 'Each row below shows one installed plugin. Tap a row to see its details, or use its menu button for more actions.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item', - 'Tap for this plugin''s details.', - 'Shows this plugin''s name, status, and version. Tap the row to see full details, including its description and version history.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item', 'Tap for this plugin''s details.', 'Shows this plugin''s name, status, and version. Tap the row to see full details, including its description and version history.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item.menu', - 'More actions for this plugin.', - 'Opens a menu with actions for this plugin, such as enable, disable, uninstall, or view details. Which actions appear depends on the plugin''s current state.'); +INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item.menu', 'More actions for this plugin.', 'Opens a menu with actions for this plugin, such as enable, disable, uninstall, or view details. Which actions appear depends on the plugin''s current state.') + ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; -- --------------------------------------------------------------------- -- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. -- --------------------------------------------------------------------- .system echo "

Plugin Manager lists every plugin currently installed in Code on the Go.

From here you can install a new plugin from a file, find more plugins online, and open any installed plugin's details or actions.

" | brotli -Z > /tmp/adfa5088-pm-toolbar.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/toolbar', 1, 12, READFILE('/tmp/adfa5088-pm-toolbar.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/toolbar', 1, 12, READFILE('/tmp/adfa5088-pm-toolbar.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

The download icon opens a webpage where you can find plugins to add to Code on the Go.

This opens in your browser or an in-app web view, outside Code on the Go itself.

" | brotli -Z > /tmp/adfa5088-pm-download.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/download', 1, 12, READFILE('/tmp/adfa5088-pm-download.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/download', 1, 12, READFILE('/tmp/adfa5088-pm-download.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

The + button installs a plugin you already have as a file.

Tap it to open a file picker, choose a plugin package (a .cgp file), and confirm the install. This does not download anything - use the download icon first if you need to find a plugin file.

" | brotli -Z > /tmp/adfa5088-pm-fab-install.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/fab/install', 1, 12, READFILE('/tmp/adfa5088-pm-fab-install.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/fab/install', 1, 12, READFILE('/tmp/adfa5088-pm-fab-install.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

This message appears when Plugin Manager has no plugins to show.

Use the download icon at the top of the screen to find plugins, or the + button to install a plugin file you already have.

" | brotli -Z > /tmp/adfa5088-pm-emptystate.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/emptystate', 1, 12, READFILE('/tmp/adfa5088-pm-emptystate.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/emptystate', 1, 12, READFILE('/tmp/adfa5088-pm-emptystate.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

This list shows every plugin installed in Code on the Go, one row per plugin.

Each row shows the plugin's name, version, and current status. Tap a row for its details, or use its menu button for more actions.

" | brotli -Z > /tmp/adfa5088-pm-list.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/list', 1, 12, READFILE('/tmp/adfa5088-pm-list.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/list', 1, 12, READFILE('/tmp/adfa5088-pm-list.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

This row represents one installed plugin.

It shows the plugin's name, version, and whether it is enabled, disabled, or failed to load. Tap the row to open its full details.

" | brotli -Z > /tmp/adfa5088-pm-item.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item', 1, 12, READFILE('/tmp/adfa5088-pm-item.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item', 1, 12, READFILE('/tmp/adfa5088-pm-item.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; .system echo "

This button opens a menu of actions for this plugin.

Depending on the plugin's current state, the menu can include enabling it, disabling it, uninstalling it, or viewing its details.

" | brotli -Z > /tmp/adfa5088-pm-item-menu.br -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-item-menu.br')); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-item-menu.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +COMMIT; From 089219b36e12c527240a298264735d94623f9929 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 00:26:53 -0700 Subject: [PATCH 14/23] ADFA-5088: Fail fast on a bad or empty Brotli payload in the docdb SQL Add `.bail on` to both scripts: without it, a mid-transaction SQL error still lets the trailing COMMIT through with whatever partial data already succeeded (verified empirically) - exactly the silent-partial- apply risk the transaction wrapping was meant to prevent. `.bail` alone isn't enough though: `.system` shell failures aren't SQL errors, so a failed or empty Brotli run leaves its target file missing or zero-length and `.bail` never sees it - the subsequent READFILE() would happily insert an empty blob as if it were real content. Add a `_content_guard` temp table (connection-local, dropped automatically, never touches the real schema) with `NOT NULL CHECK (length(content) > 0)`, and insert a throwaway READFILE() into it right before every real Content insert - turning that failure mode into a real SQL error `.bail` does catch, before it can reach the real Content table. Verified end to end against a scratch copy of the real, pristine documentation.db.save: both scripts apply cleanly (60 new preference tags + 7 plugin-manager tags - 1 deleted dead tag = +66 Tooltips, +72 Content rows, matching expectations exactly), the 5 tags with existing curated production content are untouched, content decompresses correctly, and a second run is a no-op (idempotent). Also verified the failure path directly: a simulated Brotli failure aborts the whole transaction with no partial writes. Co-Authored-By: Claude Sonnet 5 --- .../ADFA-5088-plugin-manager-tooltips.sql | 56 +++++- docs/docdb/ADFA-5088-preference-tooltips.sql | 172 +++++++++++++++++- 2 files changed, 208 insertions(+), 20 deletions(-) diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql index 8184cde159..05ab24cbb7 100644 --- a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -22,19 +22,41 @@ -- -- Apply against the real documentation.db: -- sqlite3 documentation.db < ADFA-5088-plugin-manager-tooltips.sql --- The whole script runs inside one transaction (BEGIN/COMMIT below), so a --- failure partway through leaves the database untouched rather than --- half-applied. +-- The whole script runs inside one transaction (BEGIN/COMMIT below) with +-- `.bail on`, so any failure - a bad SQL statement, or a Brotli payload +-- caught by the guard below - stops the script and leaves the database +-- untouched (the open transaction rolls back when the connection closes) +-- rather than half-applied. -- --- The Content section uses `.system echo "" | brotli -Z > /tmp/x.br` --- immediately before each `INSERT ... READFILE('/tmp/x.br')` so the --- uncompressed HTML is visible in this script. `.system` and READFILE() --- require the sqlite3 CLI (not a library binding). If `.system` is --- disabled in your sqlite3 build, run the `echo ... | brotli -Z > file` --- line yourself via a shell first, then run just the INSERT statements. - +-- For each Content row: `.system rm -f /tmp/x.br` clears any stale file, +-- `.system echo "" | brotli -Z > /tmp/x.br` writes the compressed +-- payload (the uncompressed HTML is visible right there in the command), +-- then `INSERT INTO _content_guard SELECT READFILE('/tmp/x.br')` is a +-- deliberate assertion: `.system` failures aren't SQL errors and `.bail` +-- can't see them directly, but a failed or empty Brotli run leaves +-- /tmp/x.br missing or empty, and _content_guard's `NOT NULL` + `CHECK +-- (length(content) > 0)` turn that into a real SQL error .bail does catch +-- - before the real `INSERT INTO Content` below it can run with bad data. +-- _content_guard is a TEMP table: connection-local, dropped automatically, +-- never touches the real schema. +-- +-- `.system`, `.bail`, and `READFILE()` require the sqlite3 CLI (not a +-- library binding). If `.system` is disabled in your sqlite3 build, run +-- each `rm -f`/`echo ... | brotli -Z > file` pair yourself via a shell +-- first, then run just the INSERT statements (the _content_guard +-- assertion becomes redundant at that point - the file either exists and +-- is non-empty by the time you run the script, or you'd have already +-- seen the shell command fail). + +.bail on BEGIN; +-- A temp table (connection-local, never touches the real schema) whose +-- CHECK constraint turns a silently-empty or failed Brotli payload into a +-- real SQL error .bail can catch, before it ever reaches the real Content +-- table. +CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > 0)); + -- --------------------------------------------------------------------- -- Remove the dead "plugin.manager" tag: no code path can reach it any -- more now that every widget has its own tag. Delete the TooltipButtons @@ -75,25 +97,39 @@ INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manag -- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. -- --------------------------------------------------------------------- +.system rm -f /tmp/adfa5088-pm-toolbar.br .system echo "

Plugin Manager lists every plugin currently installed in Code on the Go.

From here you can install a new plugin from a file, find more plugins online, and open any installed plugin's details or actions.

" | brotli -Z > /tmp/adfa5088-pm-toolbar.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-toolbar.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/toolbar', 1, 12, READFILE('/tmp/adfa5088-pm-toolbar.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-pm-download.br .system echo "

The download icon opens a webpage where you can find plugins to add to Code on the Go.

This opens in your browser or an in-app web view, outside Code on the Go itself.

" | brotli -Z > /tmp/adfa5088-pm-download.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-download.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/download', 1, 12, READFILE('/tmp/adfa5088-pm-download.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-pm-fab-install.br .system echo "

The + button installs a plugin you already have as a file.

Tap it to open a file picker, choose a plugin package (a .cgp file), and confirm the install. This does not download anything - use the download icon first if you need to find a plugin file.

" | brotli -Z > /tmp/adfa5088-pm-fab-install.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-fab-install.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/fab/install', 1, 12, READFILE('/tmp/adfa5088-pm-fab-install.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-pm-emptystate.br .system echo "

This message appears when Plugin Manager has no plugins to show.

Use the download icon at the top of the screen to find plugins, or the + button to install a plugin file you already have.

" | brotli -Z > /tmp/adfa5088-pm-emptystate.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-emptystate.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/emptystate', 1, 12, READFILE('/tmp/adfa5088-pm-emptystate.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-pm-list.br .system echo "

This list shows every plugin installed in Code on the Go, one row per plugin.

Each row shows the plugin's name, version, and current status. Tap a row for its details, or use its menu button for more actions.

" | brotli -Z > /tmp/adfa5088-pm-list.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-list.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/list', 1, 12, READFILE('/tmp/adfa5088-pm-list.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-pm-item.br .system echo "

This row represents one installed plugin.

It shows the plugin's name, version, and whether it is enabled, disabled, or failed to load. Tap the row to open its full details.

" | brotli -Z > /tmp/adfa5088-pm-item.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-item.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item', 1, 12, READFILE('/tmp/adfa5088-pm-item.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-pm-item-menu.br .system echo "

This button opens a menu of actions for this plugin.

Depending on the plugin's current state, the menu can include enabling it, disabling it, uninstalling it, or viewing its details.

" | brotli -Z > /tmp/adfa5088-pm-item-menu.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-item-menu.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-item-menu.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; COMMIT; diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql index ee99e347b2..a855203009 100644 --- a/docs/docdb/ADFA-5088-preference-tooltips.sql +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -21,19 +21,41 @@ -- -- Apply against the real documentation.db: -- sqlite3 documentation.db < ADFA-5088-preference-tooltips.sql --- The whole script runs inside one transaction (BEGIN/COMMIT below), so a --- failure partway through leaves the database untouched rather than --- half-applied. +-- The whole script runs inside one transaction (BEGIN/COMMIT below) with +-- `.bail on`, so any failure - a bad SQL statement, or a Brotli payload +-- caught by the guard below - stops the script and leaves the database +-- untouched (the open transaction rolls back when the connection closes) +-- rather than half-applied. -- --- The Content section uses `.system echo "" | brotli -Z > /tmp/x.br` --- immediately before each `INSERT ... READFILE('/tmp/x.br')` so the --- uncompressed HTML is visible in this script. `.system` and `READFILE()` --- require the sqlite3 CLI (not a library binding). If `.system` is disabled --- in your sqlite3 build, run the `echo ... | brotli -Z > file` line yourself --- via a shell first, then run just the INSERT statements. - +-- For each Content row: `.system rm -f /tmp/x.br` clears any stale file, +-- `.system echo "" | brotli -Z > /tmp/x.br` writes the compressed +-- payload (the uncompressed HTML is visible right there in the command), +-- then `INSERT INTO _content_guard SELECT READFILE('/tmp/x.br')` is a +-- deliberate assertion: `.system` failures aren't SQL errors and `.bail` +-- can't see them directly, but a failed or empty Brotli run leaves +-- /tmp/x.br missing or empty, and _content_guard's `NOT NULL` + `CHECK +-- (length(content) > 0)` turn that into a real SQL error .bail does catch +-- - before the real `INSERT INTO Content` below it can run with bad data. +-- _content_guard is a TEMP table: connection-local, dropped automatically, +-- never touches the real schema. +-- +-- `.system`, `.bail`, and `READFILE()` require the sqlite3 CLI (not a +-- library binding). If `.system` is disabled in your sqlite3 build, run +-- each `rm -f`/`echo ... | brotli -Z > file` pair yourself via a shell +-- first, then run just the INSERT statements (the _content_guard +-- assertion becomes redundant at that point - the file either exists and +-- is non-empty by the time you run the script, or you'd have already +-- seen the shell command fail). + +.bail on BEGIN; +-- A temp table (connection-local, never touches the real schema) whose +-- CHECK constraint turns a silently-empty or failed Brotli payload into a +-- real SQL error .bail can catch, before it ever reaches the real Content +-- table. +CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > 0)); + -- --------------------------------------------------------------------- -- Tooltips: idempotent upserts (existing empty stub rows + brand new tags, -- all in one form so the script can be re-run safely) @@ -225,199 +247,329 @@ INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about' -- then inserts the resulting compressed file with READFILE(). -- --------------------------------------------------------------------- +.system rm -f /tmp/adfa5088-prefs-general.br .system echo "

The General screen holds settings that are not specific to the editor or to a single project. Use it to set the app theme, choose a display language, and control how Code on the Go opens the last project on launch.

Changes here apply immediately and affect the whole app.

" | brotli -Z > /tmp/adfa5088-prefs-general.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general', 1, 12, READFILE('/tmp/adfa5088-prefs-general.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-general-uimode.br .system echo "

UI mode sets the color theme for Code on the Go.

  • Light: always use light colors.
  • Dark: always use dark colors.
  • Follow system: match your device's current theme, and switch automatically when it changes.
" | brotli -Z > /tmp/adfa5088-prefs-general-uimode.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-uimode.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/uimode', 1, 12, READFILE('/tmp/adfa5088-prefs-general-uimode.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-general-language.br .system echo "

Language sets the display language for the Code on the Go interface.

Choose System Default to use your device language setting, or pick one of the supported languages directly. The app restarts the affected screens to apply the change.

" | brotli -Z > /tmp/adfa5088-prefs-general-language.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-language.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/language', 1, 12, READFILE('/tmp/adfa5088-prefs-general-language.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-general-openlast.br .system echo "

Open last project controls what happens when you start Code on the Go.

Turn it on to skip the project list and go straight into your most recent project. Turn it off to see the project list every time.

" | brotli -Z > /tmp/adfa5088-prefs-general-openlast.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-openlast.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/openlast', 1, 12, READFILE('/tmp/adfa5088-prefs-general-openlast.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-general-confirmopen.br .system echo "

Confirm project opening adds a confirmation step before Code on the Go reopens your last project automatically.

Use this if you often want to switch to a different project instead of continuing the last one.

" | brotli -Z > /tmp/adfa5088-prefs-general-confirmopen.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-confirmopen.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/confirmopen', 1, 12, READFILE('/tmp/adfa5088-prefs-general-confirmopen.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor.br .system echo "

The Editor screen holds settings for the code editor: font size, tab size, word wrap, whitespace display, and other editing behavior.

Formatting options specific to XML files are on a separate sub-screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor', 1, 12, READFILE('/tmp/adfa5088-prefs-editor.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-fontsize.br .system echo "

Font size sets the text size used in the code editor, in sp units.

Choose a larger value to make code easier to read, or a smaller value to fit more code on the screen. The allowed range is 6 to 32.

" | brotli -Z > /tmp/adfa5088-prefs-editor-fontsize.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-fontsize.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/fontsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-fontsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-tabsize.br .system echo "

Tab size sets the number of spaces that one tab character represents in the editor.

Pick a value that matches your project code style: 2, 4, 6, or 8 spaces.

" | brotli -Z > /tmp/adfa5088-prefs-editor-tabsize.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-tabsize.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/tabsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-tabsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-nonprinting.br .system echo "

Show non-printing characters controls which invisible characters, such as spaces, tabs, and line breaks, the editor marks visibly.

Open this setting to choose which kinds to show: leading whitespace, trailing whitespace, whitespace between words, whitespace on empty lines, and line-break marks.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-leading.br .system echo "

Leading marks whitespace characters, such as spaces and tabs, that appear before the first visible character on a line.

Turn this on to see indentation whitespace clearly.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-leading.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-leading.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/leading', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-leading.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-trailing.br .system echo "

Trailing marks whitespace characters that appear after the last visible character on a line.

Turn this on to spot stray trailing spaces or tabs.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-trailing.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-trailing.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/trailing', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-trailing.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-inner.br .system echo "

Inner marks whitespace characters that appear between words or tokens within a line, rather than at the start or end.

Turn this on to check for irregular spacing inside a line.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-inner.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-inner.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/inner', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-inner.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-emptylines.br .system echo "

Empty lines marks whitespace characters on lines that have no visible text, only spaces or tabs.

Turn this on to spot stray whitespace on otherwise blank lines.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-emptylines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-emptylines.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/emptylines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-emptylines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br .system echo "

Line breaks draws a small marker at the end of each line to show where the line break occurs.

Turn this on to see line endings clearly, for example when comparing files with different line-ending styles.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/linebreaks', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-softtab.br .system echo "

Use soft tab controls what the editor inserts when you press the Tab key.

Turn this on to insert spaces instead of a tab character. This can help keep code consistent across editors that render tabs differently.

" | brotli -Z > /tmp/adfa5088-prefs-editor-softtab.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-softtab.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/softtab', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-softtab.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-wordwrap.br .system echo "

Word wrap breaks long lines of code into multiple visual lines so they fit within the visible width of the editor.

The underlying file is not changed; only the way it is displayed changes.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordwrap.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-wordwrap.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordwrap', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordwrap.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-magnifier.br .system echo "

Enable magnifier shows a zoomed-in view of the text around your finger when you press and hold to select text.

This makes it easier to position the cursor precisely on a small screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor-magnifier.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-magnifier.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/magnifier', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-magnifier.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-wordboundaries.br .system echo "

Use spaces as word boundaries changes how the editor decides what counts as one word when you double-tap to select text.

When on, only blank space marks the edge of a word. When off, the editor also treats punctuation, such as periods and underscores, as word boundaries.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordboundaries.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-wordboundaries.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordboundaries', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordboundaries.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-matchcase.br .system echo "

Match completions in lower case controls whether the code editor autocomplete feature is case-sensitive.

When on, typing in lower case still matches suggestions that use upper case letters, such as class names.

" | brotli -Z > /tmp/adfa5088-prefs-editor-matchcase.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-matchcase.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/matchcase', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-matchcase.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-deletelines.br .system echo "

Delete empty lines on backspace changes what happens when you press backspace on a line with no visible text.

When on, the whole empty line is removed in one step, instead of removing one whitespace character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-deletelines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-deletelines.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/deletelines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-deletelines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-smartbackspace.br .system echo "

Smart backspace indent changes what one backspace press removes when the cursor is inside leading indentation.

When on, it removes a full indent level at once. When off, it removes one character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-smartbackspace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-smartbackspace.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/smartbackspace', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-smartbackspace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-stickyscroll.br .system echo "

Sticky scroll keeps the header line of the code block you are inside, such as a class or method declaration, visible at the top of the editor while you scroll down through its contents.

This helps you keep track of context in long files.

" | brotli -Z > /tmp/adfa5088-prefs-editor-stickyscroll.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-stickyscroll.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/stickyscroll', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-stickyscroll.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-pinlines.br .system echo "

Pin line numbers keeps the line-number column fixed on the left side of the editor.

Without this, scrolling a long line horizontally can move the line numbers out of view along with the code.

" | brotli -Z > /tmp/adfa5088-prefs-editor-pinlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-pinlines.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/pinlines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-pinlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-googlestyle.br .system echo "

Use Google Java Style code formatting applies Google's published conventions for Java source code, such as indentation, spacing, and line-wrapping rules, when you format code.

Turn this on if your project follows Google's Java style guide.

" | brotli -Z > /tmp/adfa5088-prefs-editor-googlestyle.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-googlestyle.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/googlestyle', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-googlestyle.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-editor-xml.br .system echo "

XML formatting options opens a sub-screen of settings that control how Code on the Go formats XML files, separate from the general editor settings.

" | brotli -Z > /tmp/adfa5088-prefs-editor-xml.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-xml.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/xml', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-xml.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-trimfinalnewline.br .system echo "

Trim final new line removes empty lines at the very end of an XML file when the formatter runs.

This is off by default.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimfinalnewline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-trimfinalnewline.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-insertfinalnewline.br .system echo "

Insert final new line adds a single newline character at the end of an XML file if one is not already there.

Many tools expect files to end this way.

" | brotli -Z > /tmp/adfa5088-prefs-xml-insertfinalnewline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-insertfinalnewline.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/insertfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-insertfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-splitattributes.br .system echo "

Split attributes places each attribute of an XML tag on its own line when the formatter runs.

This makes tags with many attributes easier to read and to compare in version control.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattributes.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-splitattributes.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-joincdatalines.br .system echo "

Join CDATA lines combines the lines of a CDATA section into a single line when the formatter runs.

CDATA sections hold raw, unescaped text or markup inside an XML file.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincdatalines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-joincdatalines.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincdatalines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincdatalines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-joincommentlines.br .system echo "

Join comment lines combines adjacent single-line XML comments into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincommentlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-joincommentlines.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincommentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincommentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-joincontentlines.br .system echo "

Join content lines combines multiple lines of text inside a single XML element into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincontentlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-joincontentlines.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincontentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincontentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-spacebeforeclose.br .system echo "

Space before empty close tag adds one space before the closing /> of a self-closing XML tag, so the formatter produces <foo /> instead of <foo/>.

" | brotli -Z > /tmp/adfa5088-prefs-xml-spacebeforeclose.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-spacebeforeclose.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/spacebeforeclose', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-spacebeforeclose.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-preserveemptycontent.br .system echo "

Preserve empty content keeps an XML element that appears alone on an otherwise empty line as it is, instead of collapsing or moving it during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveemptycontent.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-preserveemptycontent.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveemptycontent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveemptycontent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-preserveattributes.br .system echo "

Preserve attribute line breaks keeps attributes on the separate lines you already placed them on, instead of collapsing them onto a single line during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveattributes.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-preserveattributes.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-closebracket.br .system echo "

Closing bracket on new line places the final closing bracket of a tag, including self-closing tags, on its own new line after the last attribute.

" | brotli -Z > /tmp/adfa5088-prefs-xml-closebracket.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-closebracket.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/closebracket', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-closebracket.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-trimwhitespace.br .system echo "

Trim trailing whitespace removes any spaces or tabs left at the end of each line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimwhitespace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-trimwhitespace.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimwhitespace', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimwhitespace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-maxlinewidth.br .system echo "

Maximum line width sets the number of characters allowed on a single line before the formatter wraps it onto additional lines.

The default value is 80 characters.

" | brotli -Z > /tmp/adfa5088-prefs-xml-maxlinewidth.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-maxlinewidth.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/maxlinewidth', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-maxlinewidth.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-preservenewlines.br .system echo "

Preserve new lines sets the maximum number of consecutive blank lines the formatter keeps between XML elements.

Any blank lines beyond this limit are removed. The default is 2.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preservenewlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-preservenewlines.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preservenewlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preservenewlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-splitattribindent.br .system echo "

Split attributes indent size sets the number of spaces used to indent an attribute placed on its own line by the formatter.

By default, this is half the editor's tab size.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattribindent.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-splitattribindent.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattribindent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattribindent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-xml-emptyelements.br .system echo "

Empty elements behavior controls how the formatter writes an XML element that has no content.

  • Expand: write a separate open and close tag, for example <foo></foo>.
  • Collapse: write a single self-closing tag, for example <foo/>.
  • Ignore: leave the element as it is in the source file.

The default is Collapse.

" | brotli -Z > /tmp/adfa5088-prefs-xml-emptyelements.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-emptyelements.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/emptyelements', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-emptyelements.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build.br .system echo "

The Build & Run screen holds settings for the Gradle build and for running your app: additional Gradle command-line flags, and whether to launch the app automatically after installation.

" | brotli -Z > /tmp/adfa5088-prefs-build.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build', 1, 12, READFILE('/tmp/adfa5088-prefs-build.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-autolaunch.br .system echo "

Launch app after installation controls whether Code on the Go opens your app automatically once a run finishes installing it on the device.

When off, you need to launch the app yourself.

" | brotli -Z > /tmp/adfa5088-prefs-build-autolaunch.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-autolaunch.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/autolaunch', 1, 12, READFILE('/tmp/adfa5088-prefs-build-autolaunch.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags.br .system echo "

Additional Gradle flags lets you turn on extra command-line flags that Code on the Go adds to every Gradle task it runs, such as build and sync.

Use this to get more detailed logs, use a build cache, or work offline, without typing the flags yourself each time.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags-stacktrace.br .system echo "

Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure, which can help diagnose the exact cause.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-stacktrace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-stacktrace.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--stacktrace', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-stacktrace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags-info.br .system echo "

Sets the Gradle --info flag to produce more detailed log messages as each task runs, rather than just a summary line for each task.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-info.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-info.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--info', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-info.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags-debug.br .system echo "

Sets the Gradle --debug flag. Gradle prints its most detailed log level for every task.

This produces a lot of output and can make the build noticeably slower. Use it only when you need to diagnose a specific problem.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-debug.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-debug.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--debug', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-debug.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags-scan.br .system echo "

Sets the Gradle --scan flag. After the build finishes, Gradle uploads data about the build and gives you a link to a Build Scan report.

The report shows detailed information about tasks, dependencies, and timing. This requires network access.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-scan.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-scan.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--scan', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-scan.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags-warningmodeall.br .system echo "

Sets the Gradle --warning-mode all flag. Gradle prints every individual deprecation warning during the build, instead of only a summary count at the end.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-warningmodeall.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-warningmodeall.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--warning-mode-all', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-warningmodeall.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags-buildcache.br .system echo "

Sets the Gradle --build-cache flag. Gradle reuses outputs from a previous build for any task whose inputs have not changed, instead of running that task again.

This can noticeably speed up repeated builds.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-buildcache.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-buildcache.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--build-cache', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-buildcache.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-build-flags-offline.br .system echo "

Sets the Gradle --offline flag. Gradle uses only the dependencies already downloaded to your device and does not attempt any network access.

The build fails if a required dependency has not been downloaded yet.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-offline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-offline.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--offline', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-offline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-termux.br .system echo "

The Terminal screen holds settings for the built-in terminal emulator: internal logging, keyboard behavior, crash notifications, and screen margin.

" | brotli -Z > /tmp/adfa5088-prefs-termux.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux', 1, 12, READFILE('/tmp/adfa5088-prefs-termux.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-termux-loglevel.br .system echo "

Log Level sets how much detail the terminal emulator writes to its own internal log, separate from your app logs.

Use a higher level only when troubleshooting a terminal problem, since it produces more log output.

" | brotli -Z > /tmp/adfa5088-prefs-termux-loglevel.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-loglevel.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/loglevel', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-loglevel.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-termux-keylogging.br .system echo "

Terminal View Key Logging records each key you press in the terminal to the system log.

This produces a large amount of log output and can cause performance problems, so it is off by default. Turn it on only to debug a keyboard-related issue.

" | brotli -Z > /tmp/adfa5088-prefs-termux-keylogging.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-keylogging.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/keylogging', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-keylogging.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-termux-crashreports.br .system echo "

Crash Report Notifications controls whether Code on the Go shows a notification after the terminal process crashes.

The notification lets you view or share a report about the crash. This is on by default.

" | brotli -Z > /tmp/adfa5088-prefs-termux-crashreports.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-crashreports.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/crashreports', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-crashreports.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-termux-softkeyboard.br .system echo "

Soft Keyboard Enabled turns on the terminal's own on-screen keyboard, which includes keys not found on a standard keyboard, such as Ctrl and Esc.

" | brotli -Z > /tmp/adfa5088-prefs-termux-softkeyboard.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-softkeyboard.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/softkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-softkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-termux-nohardkeyboard.br .system echo "

Soft Keyboard Only If No Hardware shows the on-screen keyboard only when no physical keyboard is connected to your device.

This is off by default, meaning the on-screen keyboard can show even with a hardware keyboard connected.

" | brotli -Z > /tmp/adfa5088-prefs-termux-nohardkeyboard.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-nohardkeyboard.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/nohardkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-nohardkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-termux-margin.br .system echo "

Terminal Margin Adjustment changes the terminal view margin to try to prevent the on-screen keyboard from covering part of the terminal or its extra keys row.

This is on by default. If it causes screen flickering on your device, turn it off.

" | brotli -Z > /tmp/adfa5088-prefs-termux-margin.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-margin.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/margin', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-margin.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-git.br .system echo "

The Git screen sets your author identity for Git: the name and email address recorded on every commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-git.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git', 1, 12, READFILE('/tmp/adfa5088-prefs-git.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-git-username.br .system echo "

User name sets the name recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-username.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-git-username.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/username', 1, 12, READFILE('/tmp/adfa5088-prefs-git-username.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-git-useremail.br .system echo "

User email sets the email address recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-useremail.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-git-useremail.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/useremail', 1, 12, READFILE('/tmp/adfa5088-prefs-git-useremail.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-pluginmanager.br .system echo "

Plugin Manager opens a screen where you can browse, install, remove, and configure plugins that extend Code on the Go with extra features.

" | brotli -Z > /tmp/adfa5088-prefs-pluginmanager.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-pluginmanager.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/pluginmanager', 1, 12, READFILE('/tmp/adfa5088-prefs-pluginmanager.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-about.br .system echo "

About Code on the Go opens a screen with details about the app, such as its version number and credits.

" | brotli -Z > /tmp/adfa5088-prefs-about.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-about.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/about', 1, 12, READFILE('/tmp/adfa5088-prefs-about.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-devoptions.br .system echo "

Developer Options holds experimental and debugging settings for Code on the Go, such as log dumping and log sending controls.

These settings are meant for troubleshooting, not everyday use.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-devoptions.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-devoptions-dumplogs.br .system echo "

Dump logs writes the Code on the Go internal logs to a file at ~/.cg/logs, inside your home directory.

Turn this on when you need to collect logs to diagnose a problem.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-dumplogs.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-devoptions-dumplogs.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/dumplogs', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-dumplogs.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +.system rm -f /tmp/adfa5088-prefs-devoptions-logsender.br .system echo "

Enable LogSender controls whether Code on the Go shows log output from the apps you run, inside its own log viewer.

This is on by default. Turn it off if you do not want to see app logs inside the IDE.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-logsender.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-devoptions-logsender.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-logsender.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; COMMIT; From e430a226ec7a2f85aaeaf9c21f21ff0003eb294e Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 00:52:42 -0700 Subject: [PATCH 15/23] ADFA-5088: Route docdb Brotli payloads through an owner-only workdir Fixed filenames directly under /tmp are guessable and world-writable, so another local user on the same machine could pre-plant a symlink or race the write/read pair between the .system echo | brotli write and the READFILE() read (CWE-377). Route every payload through an owner-only (mode 700) working directory instead: created fresh with `rm -rf` + `mkdir -m 700` (mode set atomically at creation, no window with a wider mode) right after the guard table, and removed again right before COMMIT. Applied to both scripts for consistency. Re-validated end to end against a scratch copy of the real, pristine documentation.db.save: apply, idempotent re-run, decompression, the 5 protected production tags untouched, and the fail-fast path (a broken Brotli binary) still rolls back the whole transaction with the new paths. Co-Authored-By: Claude Sonnet 5 --- .../ADFA-5088-plugin-manager-tooltips.sql | 125 ++-- docs/docdb/ADFA-5088-preference-tooltips.sql | 705 +++++++++--------- 2 files changed, 436 insertions(+), 394 deletions(-) diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql index 05ab24cbb7..347deed5ef 100644 --- a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -28,25 +28,35 @@ -- untouched (the open transaction rolls back when the connection closes) -- rather than half-applied. -- --- For each Content row: `.system rm -f /tmp/x.br` clears any stale file, --- `.system echo "" | brotli -Z > /tmp/x.br` writes the compressed --- payload (the uncompressed HTML is visible right there in the command), --- then `INSERT INTO _content_guard SELECT READFILE('/tmp/x.br')` is a --- deliberate assertion: `.system` failures aren't SQL errors and `.bail` --- can't see them directly, but a failed or empty Brotli run leaves --- /tmp/x.br missing or empty, and _content_guard's `NOT NULL` + `CHECK --- (length(content) > 0)` turn that into a real SQL error .bail does catch --- - before the real `INSERT INTO Content` below it can run with bad data. --- _content_guard is a TEMP table: connection-local, dropped automatically, --- never touches the real schema. +-- Every Brotli payload is written under /tmp/adfa5088-pm-workdir, an +-- owner-only (mode 700) directory this script creates fresh and removes +-- at the end - not bare /tmp filenames, which are guessable and world- +-- writable, so another local user could pre-plant a symlink or race the +-- write/read pair (CWE-377). `mkdir -m` sets the mode atomically at +-- creation, and the preceding `rm -rf` means each run starts from a +-- directory it fully owns rather than trusting one left over from an +-- earlier run. +-- +-- For each Content row: `.system rm -f /x.br` clears any stale +-- file, `.system echo "" | brotli -Z > /x.br` writes the +-- compressed payload (the uncompressed HTML is visible right there in +-- the command), then `INSERT INTO _content_guard SELECT +-- READFILE('/x.br')` is a deliberate assertion: `.system` +-- failures aren't SQL errors and `.bail` can't see them directly, but a +-- failed or empty Brotli run leaves the file missing or empty, and +-- _content_guard's `NOT NULL` + `CHECK (length(content) > 0)` turn that +-- into a real SQL error `.bail` does catch - before the real `INSERT INTO +-- Content` below it can run with bad data. _content_guard is a TEMP +-- table: connection-local, dropped automatically, never touches the real +-- schema. -- -- `.system`, `.bail`, and `READFILE()` require the sqlite3 CLI (not a --- library binding). If `.system` is disabled in your sqlite3 build, run --- each `rm -f`/`echo ... | brotli -Z > file` pair yourself via a shell --- first, then run just the INSERT statements (the _content_guard --- assertion becomes redundant at that point - the file either exists and --- is non-empty by the time you run the script, or you'd have already --- seen the shell command fail). +-- library binding). If `.system` is disabled in your sqlite3 build, +-- create the mode-700 working directory and run each `rm -f`/`echo ... | +-- brotli -Z > file` pair yourself via a shell first, then run just the +-- INSERT statements (the _content_guard assertion becomes redundant at +-- that point - the file either exists and is non-empty by the time you +-- run the script, or you'd have already seen the shell command fail). .bail on BEGIN; @@ -57,6 +67,16 @@ BEGIN; -- table. CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > 0)); +-- Route every Brotli payload through an owner-only (mode 700) working +-- directory instead of bare /tmp filenames: a fixed name under world- +-- writable /tmp is guessable, so another local user could pre-plant a +-- symlink or race the write/read pair. mkdir -m sets the mode atomically +-- at creation (no separate chmod, no window with a wider mode); the prior +-- rm -rf makes each run start from a clean directory it fully owns, +-- rather than trusting one left over from an earlier run. +.system rm -rf /tmp/adfa5088-pm-workdir +.system mkdir -m 700 /tmp/adfa5088-pm-workdir + -- --------------------------------------------------------------------- -- Remove the dead "plugin.manager" tag: no code path can reach it any -- more now that every widget has its own tag. Delete the TooltipButtons @@ -97,39 +117,40 @@ INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manag -- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. -- --------------------------------------------------------------------- -.system rm -f /tmp/adfa5088-pm-toolbar.br -.system echo "

Plugin Manager lists every plugin currently installed in Code on the Go.

From here you can install a new plugin from a file, find more plugins online, and open any installed plugin's details or actions.

" | brotli -Z > /tmp/adfa5088-pm-toolbar.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-toolbar.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/toolbar', 1, 12, READFILE('/tmp/adfa5088-pm-toolbar.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-pm-download.br -.system echo "

The download icon opens a webpage where you can find plugins to add to Code on the Go.

This opens in your browser or an in-app web view, outside Code on the Go itself.

" | brotli -Z > /tmp/adfa5088-pm-download.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-download.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/download', 1, 12, READFILE('/tmp/adfa5088-pm-download.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-pm-fab-install.br -.system echo "

The + button installs a plugin you already have as a file.

Tap it to open a file picker, choose a plugin package (a .cgp file), and confirm the install. This does not download anything - use the download icon first if you need to find a plugin file.

" | brotli -Z > /tmp/adfa5088-pm-fab-install.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-fab-install.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/fab/install', 1, 12, READFILE('/tmp/adfa5088-pm-fab-install.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-pm-emptystate.br -.system echo "

This message appears when Plugin Manager has no plugins to show.

Use the download icon at the top of the screen to find plugins, or the + button to install a plugin file you already have.

" | brotli -Z > /tmp/adfa5088-pm-emptystate.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-emptystate.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/emptystate', 1, 12, READFILE('/tmp/adfa5088-pm-emptystate.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-pm-list.br -.system echo "

This list shows every plugin installed in Code on the Go, one row per plugin.

Each row shows the plugin's name, version, and current status. Tap a row for its details, or use its menu button for more actions.

" | brotli -Z > /tmp/adfa5088-pm-list.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-list.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/list', 1, 12, READFILE('/tmp/adfa5088-pm-list.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-pm-item.br -.system echo "

This row represents one installed plugin.

It shows the plugin's name, version, and whether it is enabled, disabled, or failed to load. Tap the row to open its full details.

" | brotli -Z > /tmp/adfa5088-pm-item.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-item.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item', 1, 12, READFILE('/tmp/adfa5088-pm-item.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-pm-item-menu.br -.system echo "

This button opens a menu of actions for this plugin.

Depending on the plugin's current state, the menu can include enabling it, disabling it, uninstalling it, or viewing its details.

" | brotli -Z > /tmp/adfa5088-pm-item-menu.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-item-menu.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-item-menu.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br +.system echo "

Plugin Manager lists every plugin currently installed in Code on the Go.

From here you can install a new plugin from a file, find more plugins online, and open any installed plugin's details or actions.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/toolbar', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-download.br +.system echo "

The download icon opens a webpage where you can find plugins to add to Code on the Go.

This opens in your browser or an in-app web view, outside Code on the Go itself.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-download.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-download.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/download', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-download.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br +.system echo "

The + button installs a plugin you already have as a file.

Tap it to open a file picker, choose a plugin package (a .cgp file), and confirm the install. This does not download anything - use the download icon first if you need to find a plugin file.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/fab/install', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-fab-install.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br +.system echo "

This message appears when Plugin Manager has no plugins to show.

Use the download icon at the top of the screen to find plugins, or the + button to install a plugin file you already have.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/emptystate', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-emptystate.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-list.br +.system echo "

This list shows every plugin installed in Code on the Go, one row per plugin.

Each row shows the plugin's name, version, and current status. Tap a row for its details, or use its menu button for more actions.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-list.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-list.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/list', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-list.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-item.br +.system echo "

This row represents one installed plugin.

It shows the plugin's name, version, and whether it is enabled, disabled, or failed to load. Tap the row to open its full details.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-item.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br +.system echo "

This button opens a menu of actions for this plugin.

Depending on the plugin's current state, the menu can include enabling it, disabling it, uninstalling it, or viewing its details.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -rf /tmp/adfa5088-pm-workdir COMMIT; diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql index a855203009..f6fd7b6d05 100644 --- a/docs/docdb/ADFA-5088-preference-tooltips.sql +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -27,25 +27,35 @@ -- untouched (the open transaction rolls back when the connection closes) -- rather than half-applied. -- --- For each Content row: `.system rm -f /tmp/x.br` clears any stale file, --- `.system echo "" | brotli -Z > /tmp/x.br` writes the compressed --- payload (the uncompressed HTML is visible right there in the command), --- then `INSERT INTO _content_guard SELECT READFILE('/tmp/x.br')` is a --- deliberate assertion: `.system` failures aren't SQL errors and `.bail` --- can't see them directly, but a failed or empty Brotli run leaves --- /tmp/x.br missing or empty, and _content_guard's `NOT NULL` + `CHECK --- (length(content) > 0)` turn that into a real SQL error .bail does catch --- - before the real `INSERT INTO Content` below it can run with bad data. --- _content_guard is a TEMP table: connection-local, dropped automatically, --- never touches the real schema. +-- Every Brotli payload is written under /tmp/adfa5088-prefs-workdir, an +-- owner-only (mode 700) directory this script creates fresh and removes +-- at the end - not bare /tmp filenames, which are guessable and world- +-- writable, so another local user could pre-plant a symlink or race the +-- write/read pair (CWE-377). `mkdir -m` sets the mode atomically at +-- creation, and the preceding `rm -rf` means each run starts from a +-- directory it fully owns rather than trusting one left over from an +-- earlier run. +-- +-- For each Content row: `.system rm -f /x.br` clears any stale +-- file, `.system echo "" | brotli -Z > /x.br` writes the +-- compressed payload (the uncompressed HTML is visible right there in +-- the command), then `INSERT INTO _content_guard SELECT +-- READFILE('/x.br')` is a deliberate assertion: `.system` +-- failures aren't SQL errors and `.bail` can't see them directly, but a +-- failed or empty Brotli run leaves the file missing or empty, and +-- _content_guard's `NOT NULL` + `CHECK (length(content) > 0)` turn that +-- into a real SQL error `.bail` does catch - before the real `INSERT INTO +-- Content` below it can run with bad data. _content_guard is a TEMP +-- table: connection-local, dropped automatically, never touches the real +-- schema. -- -- `.system`, `.bail`, and `READFILE()` require the sqlite3 CLI (not a --- library binding). If `.system` is disabled in your sqlite3 build, run --- each `rm -f`/`echo ... | brotli -Z > file` pair yourself via a shell --- first, then run just the INSERT statements (the _content_guard --- assertion becomes redundant at that point - the file either exists and --- is non-empty by the time you run the script, or you'd have already --- seen the shell command fail). +-- library binding). If `.system` is disabled in your sqlite3 build, +-- create the mode-700 working directory and run each `rm -f`/`echo ... | +-- brotli -Z > file` pair yourself via a shell first, then run just the +-- INSERT statements (the _content_guard assertion becomes redundant at +-- that point - the file either exists and is non-empty by the time you +-- run the script, or you'd have already seen the shell command fail). .bail on BEGIN; @@ -56,6 +66,16 @@ BEGIN; -- table. CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > 0)); +-- Route every Brotli payload through an owner-only (mode 700) working +-- directory instead of bare /tmp filenames: a fixed name under world- +-- writable /tmp is guessable, so another local user could pre-plant a +-- symlink or race the write/read pair. mkdir -m sets the mode atomically +-- at creation (no separate chmod, no window with a wider mode); the prior +-- rm -rf makes each run start from a clean directory it fully owns, +-- rather than trusting one left over from an earlier run. +.system rm -rf /tmp/adfa5088-prefs-workdir +.system mkdir -m 700 /tmp/adfa5088-prefs-workdir + -- --------------------------------------------------------------------- -- Tooltips: idempotent upserts (existing empty stub rows + brand new tags, -- all in one form so the script can be re-run safely) @@ -247,329 +267,330 @@ INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about' -- then inserts the resulting compressed file with READFILE(). -- --------------------------------------------------------------------- -.system rm -f /tmp/adfa5088-prefs-general.br -.system echo "

The General screen holds settings that are not specific to the editor or to a single project. Use it to set the app theme, choose a display language, and control how Code on the Go opens the last project on launch.

Changes here apply immediately and affect the whole app.

" | brotli -Z > /tmp/adfa5088-prefs-general.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general', 1, 12, READFILE('/tmp/adfa5088-prefs-general.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-general-uimode.br -.system echo "

UI mode sets the color theme for Code on the Go.

  • Light: always use light colors.
  • Dark: always use dark colors.
  • Follow system: match your device's current theme, and switch automatically when it changes.
" | brotli -Z > /tmp/adfa5088-prefs-general-uimode.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-uimode.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/uimode', 1, 12, READFILE('/tmp/adfa5088-prefs-general-uimode.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-general-language.br -.system echo "

Language sets the display language for the Code on the Go interface.

Choose System Default to use your device language setting, or pick one of the supported languages directly. The app restarts the affected screens to apply the change.

" | brotli -Z > /tmp/adfa5088-prefs-general-language.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-language.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/language', 1, 12, READFILE('/tmp/adfa5088-prefs-general-language.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-general-openlast.br -.system echo "

Open last project controls what happens when you start Code on the Go.

Turn it on to skip the project list and go straight into your most recent project. Turn it off to see the project list every time.

" | brotli -Z > /tmp/adfa5088-prefs-general-openlast.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-openlast.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/openlast', 1, 12, READFILE('/tmp/adfa5088-prefs-general-openlast.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-general-confirmopen.br -.system echo "

Confirm project opening adds a confirmation step before Code on the Go reopens your last project automatically.

Use this if you often want to switch to a different project instead of continuing the last one.

" | brotli -Z > /tmp/adfa5088-prefs-general-confirmopen.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-general-confirmopen.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/confirmopen', 1, 12, READFILE('/tmp/adfa5088-prefs-general-confirmopen.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor.br -.system echo "

The Editor screen holds settings for the code editor: font size, tab size, word wrap, whitespace display, and other editing behavior.

Formatting options specific to XML files are on a separate sub-screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor', 1, 12, READFILE('/tmp/adfa5088-prefs-editor.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-fontsize.br -.system echo "

Font size sets the text size used in the code editor, in sp units.

Choose a larger value to make code easier to read, or a smaller value to fit more code on the screen. The allowed range is 6 to 32.

" | brotli -Z > /tmp/adfa5088-prefs-editor-fontsize.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-fontsize.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/fontsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-fontsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-tabsize.br -.system echo "

Tab size sets the number of spaces that one tab character represents in the editor.

Pick a value that matches your project code style: 2, 4, 6, or 8 spaces.

" | brotli -Z > /tmp/adfa5088-prefs-editor-tabsize.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-tabsize.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/tabsize', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-tabsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-nonprinting.br -.system echo "

Show non-printing characters controls which invisible characters, such as spaces, tabs, and line breaks, the editor marks visibly.

Open this setting to choose which kinds to show: leading whitespace, trailing whitespace, whitespace between words, whitespace on empty lines, and line-break marks.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-leading.br -.system echo "

Leading marks whitespace characters, such as spaces and tabs, that appear before the first visible character on a line.

Turn this on to see indentation whitespace clearly.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-leading.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-leading.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/leading', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-leading.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-trailing.br -.system echo "

Trailing marks whitespace characters that appear after the last visible character on a line.

Turn this on to spot stray trailing spaces or tabs.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-trailing.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-trailing.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/trailing', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-trailing.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-inner.br -.system echo "

Inner marks whitespace characters that appear between words or tokens within a line, rather than at the start or end.

Turn this on to check for irregular spacing inside a line.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-inner.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-inner.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/inner', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-inner.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-emptylines.br -.system echo "

Empty lines marks whitespace characters on lines that have no visible text, only spaces or tabs.

Turn this on to spot stray whitespace on otherwise blank lines.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-emptylines.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-emptylines.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/emptylines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-emptylines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br -.system echo "

Line breaks draws a small marker at the end of each line to show where the line break occurs.

Turn this on to see line endings clearly, for example when comparing files with different line-ending styles.

" | brotli -Z > /tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/linebreaks', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-nonprinting-linebreaks.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-softtab.br -.system echo "

Use soft tab controls what the editor inserts when you press the Tab key.

Turn this on to insert spaces instead of a tab character. This can help keep code consistent across editors that render tabs differently.

" | brotli -Z > /tmp/adfa5088-prefs-editor-softtab.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-softtab.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/softtab', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-softtab.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-wordwrap.br -.system echo "

Word wrap breaks long lines of code into multiple visual lines so they fit within the visible width of the editor.

The underlying file is not changed; only the way it is displayed changes.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordwrap.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-wordwrap.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordwrap', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordwrap.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-magnifier.br -.system echo "

Enable magnifier shows a zoomed-in view of the text around your finger when you press and hold to select text.

This makes it easier to position the cursor precisely on a small screen.

" | brotli -Z > /tmp/adfa5088-prefs-editor-magnifier.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-magnifier.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/magnifier', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-magnifier.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-wordboundaries.br -.system echo "

Use spaces as word boundaries changes how the editor decides what counts as one word when you double-tap to select text.

When on, only blank space marks the edge of a word. When off, the editor also treats punctuation, such as periods and underscores, as word boundaries.

" | brotli -Z > /tmp/adfa5088-prefs-editor-wordboundaries.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-wordboundaries.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordboundaries', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-wordboundaries.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-matchcase.br -.system echo "

Match completions in lower case controls whether the code editor autocomplete feature is case-sensitive.

When on, typing in lower case still matches suggestions that use upper case letters, such as class names.

" | brotli -Z > /tmp/adfa5088-prefs-editor-matchcase.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-matchcase.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/matchcase', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-matchcase.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-deletelines.br -.system echo "

Delete empty lines on backspace changes what happens when you press backspace on a line with no visible text.

When on, the whole empty line is removed in one step, instead of removing one whitespace character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-deletelines.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-deletelines.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/deletelines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-deletelines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-smartbackspace.br -.system echo "

Smart backspace indent changes what one backspace press removes when the cursor is inside leading indentation.

When on, it removes a full indent level at once. When off, it removes one character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-editor-smartbackspace.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-smartbackspace.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/smartbackspace', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-smartbackspace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-stickyscroll.br -.system echo "

Sticky scroll keeps the header line of the code block you are inside, such as a class or method declaration, visible at the top of the editor while you scroll down through its contents.

This helps you keep track of context in long files.

" | brotli -Z > /tmp/adfa5088-prefs-editor-stickyscroll.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-stickyscroll.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/stickyscroll', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-stickyscroll.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-pinlines.br -.system echo "

Pin line numbers keeps the line-number column fixed on the left side of the editor.

Without this, scrolling a long line horizontally can move the line numbers out of view along with the code.

" | brotli -Z > /tmp/adfa5088-prefs-editor-pinlines.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-pinlines.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/pinlines', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-pinlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-googlestyle.br -.system echo "

Use Google Java Style code formatting applies Google's published conventions for Java source code, such as indentation, spacing, and line-wrapping rules, when you format code.

Turn this on if your project follows Google's Java style guide.

" | brotli -Z > /tmp/adfa5088-prefs-editor-googlestyle.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-googlestyle.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/googlestyle', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-googlestyle.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-editor-xml.br -.system echo "

XML formatting options opens a sub-screen of settings that control how Code on the Go formats XML files, separate from the general editor settings.

" | brotli -Z > /tmp/adfa5088-prefs-editor-xml.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-editor-xml.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/xml', 1, 12, READFILE('/tmp/adfa5088-prefs-editor-xml.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-trimfinalnewline.br -.system echo "

Trim final new line removes empty lines at the very end of an XML file when the formatter runs.

This is off by default.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimfinalnewline.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-trimfinalnewline.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-insertfinalnewline.br -.system echo "

Insert final new line adds a single newline character at the end of an XML file if one is not already there.

Many tools expect files to end this way.

" | brotli -Z > /tmp/adfa5088-prefs-xml-insertfinalnewline.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-insertfinalnewline.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/insertfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-insertfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-splitattributes.br -.system echo "

Split attributes places each attribute of an XML tag on its own line when the formatter runs.

This makes tags with many attributes easier to read and to compare in version control.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattributes.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-splitattributes.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-joincdatalines.br -.system echo "

Join CDATA lines combines the lines of a CDATA section into a single line when the formatter runs.

CDATA sections hold raw, unescaped text or markup inside an XML file.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincdatalines.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-joincdatalines.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincdatalines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincdatalines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-joincommentlines.br -.system echo "

Join comment lines combines adjacent single-line XML comments into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincommentlines.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-joincommentlines.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincommentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincommentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-joincontentlines.br -.system echo "

Join content lines combines multiple lines of text inside a single XML element into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-joincontentlines.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-joincontentlines.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincontentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-joincontentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-spacebeforeclose.br -.system echo "

Space before empty close tag adds one space before the closing /> of a self-closing XML tag, so the formatter produces <foo /> instead of <foo/>.

" | brotli -Z > /tmp/adfa5088-prefs-xml-spacebeforeclose.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-spacebeforeclose.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/spacebeforeclose', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-spacebeforeclose.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-preserveemptycontent.br -.system echo "

Preserve empty content keeps an XML element that appears alone on an otherwise empty line as it is, instead of collapsing or moving it during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveemptycontent.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-preserveemptycontent.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveemptycontent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveemptycontent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-preserveattributes.br -.system echo "

Preserve attribute line breaks keeps attributes on the separate lines you already placed them on, instead of collapsing them onto a single line during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preserveattributes.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-preserveattributes.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preserveattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-closebracket.br -.system echo "

Closing bracket on new line places the final closing bracket of a tag, including self-closing tags, on its own new line after the last attribute.

" | brotli -Z > /tmp/adfa5088-prefs-xml-closebracket.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-closebracket.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/closebracket', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-closebracket.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-trimwhitespace.br -.system echo "

Trim trailing whitespace removes any spaces or tabs left at the end of each line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-xml-trimwhitespace.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-trimwhitespace.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimwhitespace', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-trimwhitespace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-maxlinewidth.br -.system echo "

Maximum line width sets the number of characters allowed on a single line before the formatter wraps it onto additional lines.

The default value is 80 characters.

" | brotli -Z > /tmp/adfa5088-prefs-xml-maxlinewidth.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-maxlinewidth.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/maxlinewidth', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-maxlinewidth.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-preservenewlines.br -.system echo "

Preserve new lines sets the maximum number of consecutive blank lines the formatter keeps between XML elements.

Any blank lines beyond this limit are removed. The default is 2.

" | brotli -Z > /tmp/adfa5088-prefs-xml-preservenewlines.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-preservenewlines.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preservenewlines', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-preservenewlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-splitattribindent.br -.system echo "

Split attributes indent size sets the number of spaces used to indent an attribute placed on its own line by the formatter.

By default, this is half the editor's tab size.

" | brotli -Z > /tmp/adfa5088-prefs-xml-splitattribindent.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-splitattribindent.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattribindent', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-splitattribindent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-xml-emptyelements.br -.system echo "

Empty elements behavior controls how the formatter writes an XML element that has no content.

  • Expand: write a separate open and close tag, for example <foo></foo>.
  • Collapse: write a single self-closing tag, for example <foo/>.
  • Ignore: leave the element as it is in the source file.

The default is Collapse.

" | brotli -Z > /tmp/adfa5088-prefs-xml-emptyelements.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-xml-emptyelements.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/emptyelements', 1, 12, READFILE('/tmp/adfa5088-prefs-xml-emptyelements.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build.br -.system echo "

The Build & Run screen holds settings for the Gradle build and for running your app: additional Gradle command-line flags, and whether to launch the app automatically after installation.

" | brotli -Z > /tmp/adfa5088-prefs-build.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build', 1, 12, READFILE('/tmp/adfa5088-prefs-build.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-autolaunch.br -.system echo "

Launch app after installation controls whether Code on the Go opens your app automatically once a run finishes installing it on the device.

When off, you need to launch the app yourself.

" | brotli -Z > /tmp/adfa5088-prefs-build-autolaunch.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-autolaunch.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/autolaunch', 1, 12, READFILE('/tmp/adfa5088-prefs-build-autolaunch.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags.br -.system echo "

Additional Gradle flags lets you turn on extra command-line flags that Code on the Go adds to every Gradle task it runs, such as build and sync.

Use this to get more detailed logs, use a build cache, or work offline, without typing the flags yourself each time.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags-stacktrace.br -.system echo "

Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure, which can help diagnose the exact cause.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-stacktrace.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-stacktrace.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--stacktrace', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-stacktrace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags-info.br -.system echo "

Sets the Gradle --info flag to produce more detailed log messages as each task runs, rather than just a summary line for each task.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-info.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-info.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--info', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-info.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags-debug.br -.system echo "

Sets the Gradle --debug flag. Gradle prints its most detailed log level for every task.

This produces a lot of output and can make the build noticeably slower. Use it only when you need to diagnose a specific problem.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-debug.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-debug.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--debug', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-debug.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags-scan.br -.system echo "

Sets the Gradle --scan flag. After the build finishes, Gradle uploads data about the build and gives you a link to a Build Scan report.

The report shows detailed information about tasks, dependencies, and timing. This requires network access.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-scan.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-scan.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--scan', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-scan.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags-warningmodeall.br -.system echo "

Sets the Gradle --warning-mode all flag. Gradle prints every individual deprecation warning during the build, instead of only a summary count at the end.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-warningmodeall.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-warningmodeall.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--warning-mode-all', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-warningmodeall.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags-buildcache.br -.system echo "

Sets the Gradle --build-cache flag. Gradle reuses outputs from a previous build for any task whose inputs have not changed, instead of running that task again.

This can noticeably speed up repeated builds.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-buildcache.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-buildcache.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--build-cache', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-buildcache.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-build-flags-offline.br -.system echo "

Sets the Gradle --offline flag. Gradle uses only the dependencies already downloaded to your device and does not attempt any network access.

The build fails if a required dependency has not been downloaded yet.

" | brotli -Z > /tmp/adfa5088-prefs-build-flags-offline.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-build-flags-offline.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--offline', 1, 12, READFILE('/tmp/adfa5088-prefs-build-flags-offline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-termux.br -.system echo "

The Terminal screen holds settings for the built-in terminal emulator: internal logging, keyboard behavior, crash notifications, and screen margin.

" | brotli -Z > /tmp/adfa5088-prefs-termux.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux', 1, 12, READFILE('/tmp/adfa5088-prefs-termux.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-termux-loglevel.br -.system echo "

Log Level sets how much detail the terminal emulator writes to its own internal log, separate from your app logs.

Use a higher level only when troubleshooting a terminal problem, since it produces more log output.

" | brotli -Z > /tmp/adfa5088-prefs-termux-loglevel.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-loglevel.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/loglevel', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-loglevel.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-termux-keylogging.br -.system echo "

Terminal View Key Logging records each key you press in the terminal to the system log.

This produces a large amount of log output and can cause performance problems, so it is off by default. Turn it on only to debug a keyboard-related issue.

" | brotli -Z > /tmp/adfa5088-prefs-termux-keylogging.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-keylogging.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/keylogging', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-keylogging.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-termux-crashreports.br -.system echo "

Crash Report Notifications controls whether Code on the Go shows a notification after the terminal process crashes.

The notification lets you view or share a report about the crash. This is on by default.

" | brotli -Z > /tmp/adfa5088-prefs-termux-crashreports.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-crashreports.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/crashreports', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-crashreports.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-termux-softkeyboard.br -.system echo "

Soft Keyboard Enabled turns on the terminal's own on-screen keyboard, which includes keys not found on a standard keyboard, such as Ctrl and Esc.

" | brotli -Z > /tmp/adfa5088-prefs-termux-softkeyboard.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-softkeyboard.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/softkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-softkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-termux-nohardkeyboard.br -.system echo "

Soft Keyboard Only If No Hardware shows the on-screen keyboard only when no physical keyboard is connected to your device.

This is off by default, meaning the on-screen keyboard can show even with a hardware keyboard connected.

" | brotli -Z > /tmp/adfa5088-prefs-termux-nohardkeyboard.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-nohardkeyboard.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/nohardkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-nohardkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-termux-margin.br -.system echo "

Terminal Margin Adjustment changes the terminal view margin to try to prevent the on-screen keyboard from covering part of the terminal or its extra keys row.

This is on by default. If it causes screen flickering on your device, turn it off.

" | brotli -Z > /tmp/adfa5088-prefs-termux-margin.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-termux-margin.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/margin', 1, 12, READFILE('/tmp/adfa5088-prefs-termux-margin.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-git.br -.system echo "

The Git screen sets your author identity for Git: the name and email address recorded on every commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-git.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git', 1, 12, READFILE('/tmp/adfa5088-prefs-git.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-git-username.br -.system echo "

User name sets the name recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-username.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-git-username.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/username', 1, 12, READFILE('/tmp/adfa5088-prefs-git-username.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-git-useremail.br -.system echo "

User email sets the email address recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-git-useremail.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-git-useremail.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/useremail', 1, 12, READFILE('/tmp/adfa5088-prefs-git-useremail.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-pluginmanager.br -.system echo "

Plugin Manager opens a screen where you can browse, install, remove, and configure plugins that extend Code on the Go with extra features.

" | brotli -Z > /tmp/adfa5088-prefs-pluginmanager.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-pluginmanager.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/pluginmanager', 1, 12, READFILE('/tmp/adfa5088-prefs-pluginmanager.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-about.br -.system echo "

About Code on the Go opens a screen with details about the app, such as its version number and credits.

" | brotli -Z > /tmp/adfa5088-prefs-about.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-about.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/about', 1, 12, READFILE('/tmp/adfa5088-prefs-about.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-devoptions.br -.system echo "

Developer Options holds experimental and debugging settings for Code on the Go, such as log dumping and log sending controls.

These settings are meant for troubleshooting, not everyday use.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-devoptions.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-devoptions-dumplogs.br -.system echo "

Dump logs writes the Code on the Go internal logs to a file at ~/.cg/logs, inside your home directory.

Turn this on when you need to collect logs to diagnose a problem.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-dumplogs.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-devoptions-dumplogs.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/dumplogs', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-dumplogs.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - -.system rm -f /tmp/adfa5088-prefs-devoptions-logsender.br -.system echo "

Enable LogSender controls whether Code on the Go shows log output from the apps you run, inside its own log viewer.

This is on by default. Turn it off if you do not want to see app logs inside the IDE.

" | brotli -Z > /tmp/adfa5088-prefs-devoptions-logsender.br -INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-devoptions-logsender.br'); -INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-devoptions-logsender.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; - +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br +.system echo "

The General screen holds settings that are not specific to the editor or to a single project. Use it to set the app theme, choose a display language, and control how Code on the Go opens the last project on launch.

Changes here apply immediately and affect the whole app.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br +.system echo "

UI mode sets the color theme for Code on the Go.

  • Light: always use light colors.
  • Dark: always use dark colors.
  • Follow system: match your device's current theme, and switch automatically when it changes.
" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/uimode', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-uimode.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br +.system echo "

Language sets the display language for the Code on the Go interface.

Choose System Default to use your device language setting, or pick one of the supported languages directly. The app restarts the affected screens to apply the change.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/language', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-language.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br +.system echo "

Open last project controls what happens when you start Code on the Go.

Turn it on to skip the project list and go straight into your most recent project. Turn it off to see the project list every time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/openlast', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-openlast.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br +.system echo "

Confirm project opening adds a confirmation step before Code on the Go reopens your last project automatically.

Use this if you often want to switch to a different project instead of continuing the last one.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/general/confirmopen', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-general-confirmopen.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br +.system echo "

The Editor screen holds settings for the code editor: font size, tab size, word wrap, whitespace display, and other editing behavior.

Formatting options specific to XML files are on a separate sub-screen.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br +.system echo "

Font size sets the text size used in the code editor, in sp units.

Choose a larger value to make code easier to read, or a smaller value to fit more code on the screen. The allowed range is 6 to 32.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/fontsize', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-fontsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br +.system echo "

Tab size sets the number of spaces that one tab character represents in the editor.

Pick a value that matches your project code style: 2, 4, 6, or 8 spaces.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/tabsize', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-tabsize.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br +.system echo "

Show non-printing characters controls which invisible characters, such as spaces, tabs, and line breaks, the editor marks visibly.

Open this setting to choose which kinds to show: leading whitespace, trailing whitespace, whitespace between words, whitespace on empty lines, and line-break marks.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br +.system echo "

Leading marks whitespace characters, such as spaces and tabs, that appear before the first visible character on a line.

Turn this on to see indentation whitespace clearly.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/leading', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-leading.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br +.system echo "

Trailing marks whitespace characters that appear after the last visible character on a line.

Turn this on to spot stray trailing spaces or tabs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/trailing', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-trailing.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br +.system echo "

Inner marks whitespace characters that appear between words or tokens within a line, rather than at the start or end.

Turn this on to check for irregular spacing inside a line.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/inner', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-inner.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br +.system echo "

Empty lines marks whitespace characters on lines that have no visible text, only spaces or tabs.

Turn this on to spot stray whitespace on otherwise blank lines.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/emptylines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-emptylines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br +.system echo "

Line breaks draws a small marker at the end of each line to show where the line break occurs.

Turn this on to see line endings clearly, for example when comparing files with different line-ending styles.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/nonprinting/linebreaks', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-nonprinting-linebreaks.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br +.system echo "

Use soft tab controls what the editor inserts when you press the Tab key.

Turn this on to insert spaces instead of a tab character. This can help keep code consistent across editors that render tabs differently.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/softtab', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-softtab.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br +.system echo "

Word wrap breaks long lines of code into multiple visual lines so they fit within the visible width of the editor.

The underlying file is not changed; only the way it is displayed changes.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordwrap', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordwrap.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br +.system echo "

Enable magnifier shows a zoomed-in view of the text around your finger when you press and hold to select text.

This makes it easier to position the cursor precisely on a small screen.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/magnifier', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-magnifier.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br +.system echo "

Use spaces as word boundaries changes how the editor decides what counts as one word when you double-tap to select text.

When on, only blank space marks the edge of a word. When off, the editor also treats punctuation, such as periods and underscores, as word boundaries.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/wordboundaries', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-wordboundaries.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br +.system echo "

Match completions in lower case controls whether the code editor autocomplete feature is case-sensitive.

When on, typing in lower case still matches suggestions that use upper case letters, such as class names.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/matchcase', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-matchcase.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br +.system echo "

Delete empty lines on backspace changes what happens when you press backspace on a line with no visible text.

When on, the whole empty line is removed in one step, instead of removing one whitespace character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/deletelines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-deletelines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br +.system echo "

Smart backspace indent changes what one backspace press removes when the cursor is inside leading indentation.

When on, it removes a full indent level at once. When off, it removes one character at a time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/smartbackspace', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-smartbackspace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br +.system echo "

Sticky scroll keeps the header line of the code block you are inside, such as a class or method declaration, visible at the top of the editor while you scroll down through its contents.

This helps you keep track of context in long files.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/stickyscroll', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-stickyscroll.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br +.system echo "

Pin line numbers keeps the line-number column fixed on the left side of the editor.

Without this, scrolling a long line horizontally can move the line numbers out of view along with the code.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/pinlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-pinlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br +.system echo "

Use Google Java Style code formatting applies Google's published conventions for Java source code, such as indentation, spacing, and line-wrapping rules, when you format code.

Turn this on if your project follows Google's Java style guide.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/googlestyle', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-googlestyle.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br +.system echo "

XML formatting options opens a sub-screen of settings that control how Code on the Go formats XML files, separate from the general editor settings.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/editor/xml', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-editor-xml.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br +.system echo "

Trim final new line removes empty lines at the very end of an XML file when the formatter runs.

This is off by default.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br +.system echo "

Insert final new line adds a single newline character at the end of an XML file if one is not already there.

Many tools expect files to end this way.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/insertfinalnewline', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-insertfinalnewline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br +.system echo "

Split attributes places each attribute of an XML tag on its own line when the formatter runs.

This makes tags with many attributes easier to read and to compare in version control.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br +.system echo "

Join CDATA lines combines the lines of a CDATA section into a single line when the formatter runs.

CDATA sections hold raw, unescaped text or markup inside an XML file.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincdatalines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincdatalines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br +.system echo "

Join comment lines combines adjacent single-line XML comments into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincommentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincommentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br +.system echo "

Join content lines combines multiple lines of text inside a single XML element into one line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/joincontentlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-joincontentlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br +.system echo "

Space before empty close tag adds one space before the closing /> of a self-closing XML tag, so the formatter produces <foo /> instead of <foo/>.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/spacebeforeclose', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-spacebeforeclose.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br +.system echo "

Preserve empty content keeps an XML element that appears alone on an otherwise empty line as it is, instead of collapsing or moving it during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveemptycontent', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveemptycontent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br +.system echo "

Preserve attribute line breaks keeps attributes on the separate lines you already placed them on, instead of collapsing them onto a single line during formatting.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preserveattributes', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preserveattributes.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br +.system echo "

Closing bracket on new line places the final closing bracket of a tag, including self-closing tags, on its own new line after the last attribute.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/closebracket', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-closebracket.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br +.system echo "

Trim trailing whitespace removes any spaces or tabs left at the end of each line when the formatter runs.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/trimwhitespace', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-trimwhitespace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br +.system echo "

Maximum line width sets the number of characters allowed on a single line before the formatter wraps it onto additional lines.

The default value is 80 characters.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/maxlinewidth', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-maxlinewidth.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br +.system echo "

Preserve new lines sets the maximum number of consecutive blank lines the formatter keeps between XML elements.

Any blank lines beyond this limit are removed. The default is 2.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/preservenewlines', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-preservenewlines.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br +.system echo "

Split attributes indent size sets the number of spaces used to indent an attribute placed on its own line by the formatter.

By default, this is half the editor's tab size.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/splitattribindent', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-splitattribindent.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br +.system echo "

Empty elements behavior controls how the formatter writes an XML element that has no content.

  • Expand: write a separate open and close tag, for example <foo></foo>.
  • Collapse: write a single self-closing tag, for example <foo/>.
  • Ignore: leave the element as it is in the source file.

The default is Collapse.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/xml/emptyelements', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-xml-emptyelements.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br +.system echo "

The Build & Run screen holds settings for the Gradle build and for running your app: additional Gradle command-line flags, and whether to launch the app automatically after installation.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br +.system echo "

Launch app after installation controls whether Code on the Go opens your app automatically once a run finishes installing it on the device.

When off, you need to launch the app yourself.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/autolaunch', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-autolaunch.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br +.system echo "

Additional Gradle flags lets you turn on extra command-line flags that Code on the Go adds to every Gradle task it runs, such as build and sync.

Use this to get more detailed logs, use a build cache, or work offline, without typing the flags yourself each time.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br +.system echo "

Sets the Gradle --stacktrace flag. When a build fails, Gradle prints the full stack trace of the failure, which can help diagnose the exact cause.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--stacktrace', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-stacktrace.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br +.system echo "

Sets the Gradle --info flag to produce more detailed log messages as each task runs, rather than just a summary line for each task.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--info', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-info.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br +.system echo "

Sets the Gradle --debug flag. Gradle prints its most detailed log level for every task.

This produces a lot of output and can make the build noticeably slower. Use it only when you need to diagnose a specific problem.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--debug', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-debug.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br +.system echo "

Sets the Gradle --scan flag. After the build finishes, Gradle uploads data about the build and gives you a link to a Build Scan report.

The report shows detailed information about tasks, dependencies, and timing. This requires network access.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--scan', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-scan.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br +.system echo "

Sets the Gradle --warning-mode all flag. Gradle prints every individual deprecation warning during the build, instead of only a summary count at the end.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--warning-mode-all', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-warningmodeall.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br +.system echo "

Sets the Gradle --build-cache flag. Gradle reuses outputs from a previous build for any task whose inputs have not changed, instead of running that task again.

This can noticeably speed up repeated builds.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--build-cache', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-buildcache.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br +.system echo "

Sets the Gradle --offline flag. Gradle uses only the dependencies already downloaded to your device and does not attempt any network access.

The build fails if a required dependency has not been downloaded yet.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/build/flags/--offline', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-build-flags-offline.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br +.system echo "

The Terminal screen holds settings for the built-in terminal emulator: internal logging, keyboard behavior, crash notifications, and screen margin.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br +.system echo "

Log Level sets how much detail the terminal emulator writes to its own internal log, separate from your app logs.

Use a higher level only when troubleshooting a terminal problem, since it produces more log output.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/loglevel', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-loglevel.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br +.system echo "

Terminal View Key Logging records each key you press in the terminal to the system log.

This produces a large amount of log output and can cause performance problems, so it is off by default. Turn it on only to debug a keyboard-related issue.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/keylogging', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-keylogging.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br +.system echo "

Crash Report Notifications controls whether Code on the Go shows a notification after the terminal process crashes.

The notification lets you view or share a report about the crash. This is on by default.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/crashreports', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-crashreports.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br +.system echo "

Soft Keyboard Enabled turns on the terminal's own on-screen keyboard, which includes keys not found on a standard keyboard, such as Ctrl and Esc.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/softkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-softkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br +.system echo "

Soft Keyboard Only If No Hardware shows the on-screen keyboard only when no physical keyboard is connected to your device.

This is off by default, meaning the on-screen keyboard can show even with a hardware keyboard connected.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/nohardkeyboard', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-nohardkeyboard.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br +.system echo "

Terminal Margin Adjustment changes the terminal view margin to try to prevent the on-screen keyboard from covering part of the terminal or its extra keys row.

This is on by default. If it causes screen flickering on your device, turn it off.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/termux/margin', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-termux-margin.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br +.system echo "

The Git screen sets your author identity for Git: the name and email address recorded on every commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br +.system echo "

User name sets the name recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/username', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-username.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br +.system echo "

User email sets the email address recorded as the author on every Git commit you make from Code on the Go.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/git/useremail', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-git-useremail.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br +.system echo "

Plugin Manager opens a screen where you can browse, install, remove, and configure plugins that extend Code on the Go with extra features.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/pluginmanager', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-pluginmanager.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br +.system echo "

About Code on the Go opens a screen with details about the app, such as its version number and credits.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/about', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-about.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br +.system echo "

Developer Options holds experimental and debugging settings for Code on the Go, such as log dumping and log sending controls.

These settings are meant for troubleshooting, not everyday use.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br +.system echo "

Dump logs writes the Code on the Go internal logs to a file at ~/.cg/logs, inside your home directory.

Turn this on when you need to collect logs to diagnose a problem.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/dumplogs', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-dumplogs.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br +.system echo "

Enable LogSender controls whether Code on the Go shows log output from the apps you run, inside its own log viewer.

This is on by default. Turn it off if you do not want to see app logs inside the IDE.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br +INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br'); +INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; + +.system rm -rf /tmp/adfa5088-prefs-workdir COMMIT; From 3bc3628ddcacf4fe9c9ab8396a482b85fcf544fd Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 06:08:00 -0700 Subject: [PATCH 16/23] ADFA-5088: Fix tooltip long-press coverage gaps from the per-row refactor - PreferencesActivity's toolbar had zero long-press coverage after the screen-wide GestureDetector was removed; give it its own listener (PREFS_TOP). - Every category header (Common, Interface, Gradle, Git author identity, Termux's three sub-groups, etc.) had no tooltipTag of its own, so long-pressing one fell through to the generic root tooltip regardless of which screen it was actually on - misleading rather than merely absent. Give each category its own tag, reusing the screen's tag since a category header represents a section of that screen. - A long-press landing on empty RecyclerView space (no row under the touch point, e.g. short screens like Developer Options or About) silently showed nothing. Fall back to the current screen's own tag instead - passed down via a new EXTRA_SCREEN_TOOLTIP_TAG fragment argument, set alongside EXTRA_CHILDREN wherever a screen is built. - The long-press callback closed over `listView` and called requireContext() without lifecycle guards; a fragment swap mid- gesture (e.g. onResume's reloadRootFragmentIfContributedRowsChanged) could fire the pending GestureDetector callback against a torn-down fragment. Capture the RecyclerView once, and guard on isAdded/context before touching either. - PropertyBasedMultiChoicePreference kept two independently-maintained parallel maps (getProperties/getEntryTooltipTags) keyed by the same string labels, with a silent fallback on any drift between them. Replace both with a single List (label + property + tag) so a future edit can't update one without the other. Co-Authored-By: Claude Sonnet 5 --- .../activities/PreferencesActivity.kt | 7 +++ .../fragments/IDEPreferencesFragment.kt | 44 +++++++++++++++---- .../preferences/buildAndRunPrefExts.kt | 35 +++++---------- .../androidide/preferences/commonPrefExts.kt | 22 +++++----- .../androidide/preferences/editorPrefExts.kt | 26 ++++------- .../androidide/preferences/generalPrefExts.kt | 2 + .../androidide/preferences/gitPrefExts.kt | 3 +- .../androidide/preferences/javaPrefExts.kt | 1 + .../androidide/preferences/termuxPrefsExt.kt | 9 ++-- .../androidide/preferences/xmlPrefExts.kt | 3 +- 10 files changed, 86 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt index 5641862d81..044add0dc8 100755 --- a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt @@ -26,6 +26,8 @@ import com.itsaky.androidide.R import com.itsaky.androidide.app.EdgeToEdgeIDEActivity import com.itsaky.androidide.databinding.ActivityPreferencesBinding import com.itsaky.androidide.fragments.IDEPreferencesFragment +import com.itsaky.androidide.idetooltips.TooltipManager +import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.preferences.PluginSettingsEntryPreference import com.itsaky.androidide.preferences.addRootPreferences import com.itsaky.androidide.preferences.pluginSettingsPreferences @@ -54,6 +56,10 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { supportActionBar!!.setDisplayHomeAsUpEnabled(true) binding.toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() } + binding.toolbar.setOnLongClickListener { + TooltipManager.showIdeCategoryTooltip(this, binding.toolbar, TooltipTag.PREFS_TOP) + true + } feedbackButtonManager = FeedbackButtonManager( @@ -89,6 +95,7 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { IDEPreferencesFragment.EXTRA_CHILDREN, ArrayList(prefs.children), ) + args.putString(IDEPreferencesFragment.EXTRA_SCREEN_TOOLTIP_TAG, TooltipTag.PREFS_TOP) // A fresh instance every time: arguments cannot be set on a fragment whose state was saved. loadFragment(IDEPreferencesFragment().also { it.arguments = args }) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt index ef0164407d..20fdc1644e 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt @@ -25,6 +25,7 @@ import android.view.ViewGroup import androidx.preference.PreferenceCategory import androidx.preference.PreferenceGroup import androidx.preference.PreferenceGroupAdapter +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.transition.MaterialSharedAxis import com.itsaky.androidide.idetooltips.TooltipManager import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TOP @@ -39,6 +40,12 @@ class IDEPreferencesFragment : BasePreferenceFragment() { /** Every preference in this screen, including nested categories' children, keyed by its key. */ private var tooltipTagsByKey: Map = emptyMap() + /** + * This screen's own tag - the fallback for a long-press that lands on empty RecyclerView + * space (no row under the touch point) or on a row with no tooltipTag of its own. + */ + private var screenTooltipTag: String = PREFS_TOP + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -63,6 +70,7 @@ class IDEPreferencesFragment : BasePreferenceFragment() { @Suppress("DEPRECATION") this.children = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() this.tooltipTagsByKey = collectTooltipTags(this.children) + this.screenTooltipTag = arguments?.getString(EXTRA_SCREEN_TOOLTIP_TAG)?.takeIf { it.isNotEmpty() } ?: PREFS_TOP preferenceScreen.removeAll() addChildren(this.children, preferenceScreen) @@ -74,19 +82,37 @@ class IDEPreferencesFragment : BasePreferenceFragment() { ) { super.onViewCreated(view, savedInstanceState) - listView.onLongPress { e -> - val row = listView.findChildViewUnder(e.x, e.y) ?: return@onLongPress - val position = listView.getChildAdapterPosition(row) - if (position == androidx.recyclerview.widget.RecyclerView.NO_POSITION) { + // Captured once: re-reading the `listView` property from inside the callback (which can + // fire after a delay) would hit a field PreferenceFragmentCompat clears in onDestroyView. + val recyclerView = listView + + recyclerView.onLongPress { e -> + if (!isAdded) { return@onLongPress } + val ctx = context ?: return@onLongPress + + val row = recyclerView.findChildViewUnder(e.x, e.y) + val tag = row?.let { resolveTooltipTag(recyclerView, it) } ?: screenTooltipTag + val anchor = row ?: recyclerView - val key = (listView.adapter as? PreferenceGroupAdapter)?.getItem(position)?.key ?: return@onLongPress - val tag = tooltipTagsByKey[key]?.takeIf { it.isNotEmpty() } ?: PREFS_TOP + anchor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + TooltipManager.showIdeCategoryTooltip(ctx, anchor, tag) + } + } - row.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - TooltipManager.showIdeCategoryTooltip(requireContext(), row, tag) + /** The row's own tooltipTag, or null if there's no row at that position or it has none. */ + private fun resolveTooltipTag( + recyclerView: RecyclerView, + row: View, + ): String? { + val position = recyclerView.getChildAdapterPosition(row) + if (position == RecyclerView.NO_POSITION) { + return null } + + val key = (recyclerView.adapter as? PreferenceGroupAdapter)?.getItem(position)?.key ?: return null + return tooltipTagsByKey[key]?.takeIf { it.isNotEmpty() } } internal fun collectTooltipTags(children: List): Map { @@ -114,6 +140,7 @@ class IDEPreferencesFragment : BasePreferenceFragment() { if (child is IPreferenceScreen) { preference.fragment = IDEPreferencesFragment::class.java.name preference.extras.putParcelableArrayList(EXTRA_CHILDREN, ArrayList(child.children)) + preference.extras.putString(EXTRA_SCREEN_TOOLTIP_TAG, child.tooltipTag) pref.addPreference(preference) continue @@ -131,5 +158,6 @@ class IDEPreferencesFragment : BasePreferenceFragment() { companion object { const val EXTRA_CHILDREN = "ide.preferences.fragment.children" + const val EXTRA_SCREEN_TOOLTIP_TAG = "ide.preferences.fragment.screenTooltipTag" } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt index 5699a47e1f..3357ce9b8d 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt @@ -42,7 +42,6 @@ import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize -import kotlin.reflect.KMutableProperty0 @Parcelize class BuildAndRunPreferences( @@ -64,6 +63,7 @@ private class GradleOptions( override val key: String = "idepref_build_gradle", override val title: Int = string.gradle, override val children: List = mutableListOf(), + override val tooltipTag: String = PREFS_BUILD_RUN, ) : IPreferenceGroup() { init { @@ -82,27 +82,15 @@ private class GradleCommands( @IgnoredOnParcel override val tooltipTag: String = PREFS_BUILDRUN_FLAGS - override fun getProperties(): Map> { - return linkedMapOf( - "--stacktrace" to ::isStacktraceEnabled, - "--info" to ::isInfoEnabled, - "--debug" to ::isDebugEnabled, - "--scan" to ::isScanEnabled, - "--warning-mode all" to ::isWarningModeAllEnabled, - "--build-cache" to ::isBuildCacheEnabled, - "--offline" to ::isOfflineEnabled, - ) - } - - override fun getEntryTooltipTags(): Map { - return mapOf( - "--stacktrace" to PREFS_BUILDRUN_FLAGS_STACKTRACE, - "--info" to PREFS_BUILDRUN_FLAGS_INFO, - "--debug" to PREFS_BUILDRUN_FLAGS_DEBUG, - "--scan" to PREFS_BUILDRUN_FLAGS_SCAN, - "--warning-mode all" to PREFS_BUILDRUN_FLAGS_WARNINGMODEALL, - "--build-cache" to PREFS_BUILDRUN_FLAGS_BUILDCACHE, - "--offline" to PREFS_BUILDRUN_FLAGS_OFFLINE, + override fun getProperties(): List { + return listOf( + PropertyEntry("--stacktrace", ::isStacktraceEnabled, PREFS_BUILDRUN_FLAGS_STACKTRACE), + PropertyEntry("--info", ::isInfoEnabled, PREFS_BUILDRUN_FLAGS_INFO), + PropertyEntry("--debug", ::isDebugEnabled, PREFS_BUILDRUN_FLAGS_DEBUG), + PropertyEntry("--scan", ::isScanEnabled, PREFS_BUILDRUN_FLAGS_SCAN), + PropertyEntry("--warning-mode all", ::isWarningModeAllEnabled, PREFS_BUILDRUN_FLAGS_WARNINGMODEALL), + PropertyEntry("--build-cache", ::isBuildCacheEnabled, PREFS_BUILDRUN_FLAGS_BUILDCACHE), + PropertyEntry("--offline", ::isOfflineEnabled, PREFS_BUILDRUN_FLAGS_OFFLINE), ) } } @@ -112,7 +100,8 @@ private class GradleCommands( private class RunOptions( override val key: String = "ide.build.runOptions", override val title: Int = R.string.title_run_options, - override val children: List = mutableListOf() + override val children: List = mutableListOf(), + override val tooltipTag: String = PREFS_BUILD_RUN, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt index d803d5fe08..8c67778d38 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt @@ -23,23 +23,21 @@ import kotlin.reflect.KMutableProperty0 internal abstract class PropertyBasedMultiChoicePreference : MultiChoicePreference() { -abstract fun getProperties(): Map> +/** One checkbox: its label, the property it toggles, and its own tooltip tag (if any). */ +data class PropertyEntry( + val label: String, + val property: KMutableProperty0, + val tooltipTag: String = "", +) -/** Tooltip tags for individual entries, keyed by the same label used in [getProperties]. */ -open fun getEntryTooltipTags(): Map = emptyMap() +abstract fun getProperties(): List override fun getEntries(preference: Preference): Array { val properties = getProperties() - val entryTooltipTags = getEntryTooltipTags() - val entries = Array(properties.size) { PreferenceChoices.Entry.EMPTY } - - var index = 0 - properties.forEach { (key, property) -> - entries[index] = PreferenceChoices.Entry(key, property.get(), property, entryTooltipTags[key] ?: "") - ++index + return Array(properties.size) { i -> + val entry = properties[i] + PreferenceChoices.Entry(entry.label, entry.property.get(), entry.property, entry.tooltipTag) } - - return entries } override fun onChoicesConfirmed( diff --git a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt index c23aebead3..be8640210c 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt @@ -40,7 +40,6 @@ import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize -import kotlin.reflect.KMutableProperty0 @Parcelize class EditorPreferencesScreen( @@ -63,6 +62,7 @@ private class CommonConfigurations( override val key: String = "idepref_editor_common", override val title: Int = string.idepref_editor_category_common, override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR, ) : IPreferenceGroup() { init { @@ -165,23 +165,13 @@ override val icon: Int? = drawable.ic_drawing, override val tooltipTag: String = TooltipTag.PREFS_EDITOR_NONPRINTING, ) : PropertyBasedMultiChoicePreference() { -override fun getProperties(): Map> { - return linkedMapOf( - "Leading" to EditorPreferences::drawLeadingWs, - "Trailing" to EditorPreferences::drawTrailingWs, - "Inner" to EditorPreferences::drawInnerWs, - "Empty lines" to EditorPreferences::drawEmptyLineWs, - "Line breaks" to EditorPreferences::drawLineBreak - ) -} - -override fun getEntryTooltipTags(): Map { - return mapOf( - "Leading" to TooltipTag.PREFS_EDITOR_NONPRINTING_LEADING, - "Trailing" to TooltipTag.PREFS_EDITOR_NONPRINTING_TRAILING, - "Inner" to TooltipTag.PREFS_EDITOR_NONPRINTING_INNER, - "Empty lines" to TooltipTag.PREFS_EDITOR_NONPRINTING_EMPTYLINES, - "Line breaks" to TooltipTag.PREFS_EDITOR_NONPRINTING_LINEBREAKS +override fun getProperties(): List { + return listOf( + PropertyEntry("Leading", EditorPreferences::drawLeadingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_LEADING), + PropertyEntry("Trailing", EditorPreferences::drawTrailingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_TRAILING), + PropertyEntry("Inner", EditorPreferences::drawInnerWs, TooltipTag.PREFS_EDITOR_NONPRINTING_INNER), + PropertyEntry("Empty lines", EditorPreferences::drawEmptyLineWs, TooltipTag.PREFS_EDITOR_NONPRINTING_EMPTYLINES), + PropertyEntry("Line breaks", EditorPreferences::drawLineBreak, TooltipTag.PREFS_EDITOR_NONPRINTING_LINEBREAKS), ) } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt index 1ccd6c595f..0d946e7377 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt @@ -54,6 +54,7 @@ class InterfaceConfig( override val key: String = "idepref_general_interface", override val title: Int = string.title_interface, override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_GENERAL, ) : IPreferenceGroup() { init { @@ -67,6 +68,7 @@ class ProjectConfig( override val key: String = "idepref_general_project", override val title: Int = R.string.idepref_general_projectConfig, override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_GENERAL, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt index 468ef55af2..c9fd6459f2 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt @@ -28,7 +28,8 @@ class GitAuthorConfig( override val key: String = "idepref_git_author", override val title: Int = R.string.idepref_git_author_title, override val summary: Int? = R.string.idepref_git_author_summary, - override val children: List = mutableListOf() + override val children: List = mutableListOf(), + override val tooltipTag: String = PREFS_GIT, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt index 44197b1985..783152f467 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt @@ -29,6 +29,7 @@ internal class JavaCodeConfigurations( override val key: String = "idepref_editor_java", override val title: Int = string.idepref_editor_category_java, override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt index db94e5397b..320bc38e36 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt @@ -97,7 +97,8 @@ init { class TermuxDebuggingPreferences( override val key: String = KEY_TERMUX_DEBUGGING_PREFERENCES, override val title: Int = R.string.termux_debugging_preferences_title, -override val children: List = mutableListOf() +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceGroup() { init { @@ -193,7 +194,8 @@ tooltipTag = PREFS_TERMUX_CRASHREPORTS, class TermuxKeyboardPreferences( override val key: String = KEY_TERMUX_KBD_PREFERENCES, override val title: Int = R.string.termux_keyboard_header, -override val children: List = mutableListOf() +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceGroup() { init { @@ -246,7 +248,8 @@ tooltipTag = PREFS_TERMUX_NOHARDKEYBOARD, class TermuxViewPreferences( override val key: String = KEY_TERMUX_VIEW_PREFERENCES, override val title: Int = R.string.termux_terminal_view_view_header, -override val children: List = mutableListOf() +override val children: List = mutableListOf(), +override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt index f1fc6e163f..8f202b229f 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefExts.kt @@ -28,7 +28,8 @@ import org.eclipse.lemminx.dom.builder.EmptyElements class XMLPreferencesScreen( override val key: String = "idepref_editor_xml", override val title: Int = string.xml, -override val children: List = mutableListOf() +override val children: List = mutableListOf(), +override val tooltipTag: String = TooltipTag.PREFS_EDITOR_XML, ) : IPreferenceGroup() { init { From df577abfb36c55c114dd5423841b6071b62c3c87 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 06:08:16 -0700 Subject: [PATCH 17/23] ADFA-5088: Reuse displayTooltipOnLongPress in Plugin Manager instead of duplicating it PluginManagerActivity and PluginListAdapter hand-rolled `setOnLongClickListener { TooltipManager.showIdeCategoryTooltip(...); true }` at 6 call sites; idetooltips already has a View.displayTooltipOnLongPress extension for exactly this. A future behavior change to this wiring now only has to be made once. Co-Authored-By: Claude Sonnet 5 --- .../activities/PluginManagerActivity.kt | 32 ++++++------------- .../androidide/adapters/PluginListAdapter.kt | 24 ++++++++------ 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt index 7f477c69b5..eb3d8f71a2 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt @@ -24,7 +24,7 @@ import com.itsaky.androidide.R import com.itsaky.androidide.adapters.PluginListAdapter import com.itsaky.androidide.app.EdgeToEdgeIDEActivity import com.itsaky.androidide.databinding.ActivityPluginManagerBinding -import com.itsaky.androidide.idetooltips.TooltipManager +import com.itsaky.androidide.idetooltips.TooltipCategory import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.plugins.PluginInfo import com.itsaky.androidide.ui.models.PluginManagerUiEffect @@ -32,6 +32,7 @@ import com.itsaky.androidide.ui.models.PluginManagerUiEvent import com.itsaky.androidide.utils.DURATION_INDEFINITE import com.itsaky.androidide.utils.DialogUtils.showRestartPrompt import com.itsaky.androidide.utils.UrlManager +import com.itsaky.androidide.utils.displayTooltipOnLongPress import com.itsaky.androidide.utils.errorIcon import com.itsaky.androidide.utils.flashError import com.itsaky.androidide.utils.flashSuccess @@ -119,9 +120,8 @@ class PluginManagerActivity : EdgeToEdgeIDEActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_plugin_manager, menu) binding.toolbar.post { - binding.toolbar.findViewById(R.id.action_discover_plugins)?.setOnLongClickListener { view -> - TooltipManager.showIdeCategoryTooltip(this, view, TooltipTag.PLUGIN_MANAGER_DOWNLOAD) - true + binding.toolbar.findViewById(R.id.action_discover_plugins)?.let { view -> + view.displayTooltipOnLongPress(this, view, TooltipCategory.CATEGORY_IDE, TooltipTag.PLUGIN_MANAGER_DOWNLOAD) } } return true @@ -177,25 +177,13 @@ class PluginManagerActivity : EdgeToEdgeIDEActivity() { } private fun setupTooltipLongPress() { - val showTooltip: (View, String) -> Unit = { view, tag -> - TooltipManager.showIdeCategoryTooltip(this, view, tag) - } - binding.toolbar.setOnLongClickListener { - showTooltip(it, TooltipTag.PLUGIN_MANAGER_TOOLBAR) - true - } - binding.fabInstallPlugin.setOnLongClickListener { - showTooltip(it, TooltipTag.PLUGIN_MANAGER_FAB_INSTALL) - true - } - binding.emptyState.setOnLongClickListener { - showTooltip(it, TooltipTag.PLUGIN_MANAGER_EMPTY_STATE) - true - } - binding.recyclerView.setOnLongClickListener { - showTooltip(it, TooltipTag.PLUGIN_MANAGER_LIST) - true + val show: (View, String) -> Unit = { view, tag -> + view.displayTooltipOnLongPress(this, view, TooltipCategory.CATEGORY_IDE, tag) } + show(binding.toolbar, TooltipTag.PLUGIN_MANAGER_TOOLBAR) + show(binding.fabInstallPlugin, TooltipTag.PLUGIN_MANAGER_FAB_INSTALL) + show(binding.emptyState, TooltipTag.PLUGIN_MANAGER_EMPTY_STATE) + show(binding.recyclerView, TooltipTag.PLUGIN_MANAGER_LIST) } private fun setupFeedbackButton() { diff --git a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt index 769addb453..cfd6a9bfd0 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt @@ -14,9 +14,10 @@ import com.bumptech.glide.Glide import com.bumptech.glide.signature.ObjectKey import com.itsaky.androidide.R import com.itsaky.androidide.databinding.ItemPluginBinding -import com.itsaky.androidide.idetooltips.TooltipManager +import com.itsaky.androidide.idetooltips.TooltipCategory import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.plugins.PluginInfo +import com.itsaky.androidide.utils.displayTooltipOnLongPress import com.itsaky.androidide.utils.isSystemInDarkMode import java.io.File @@ -115,21 +116,24 @@ class PluginListAdapter( btnMenu.setOnClickListener { view -> showPopupMenu(view, plugin) } - btnMenu.setOnLongClickListener { - TooltipManager.showIdeCategoryTooltip(it.context, it, TooltipTag.PLUGIN_MANAGER_ITEM_MENU) - true - } + btnMenu.displayTooltipOnLongPress( + itemView.context, + btnMenu, + TooltipCategory.CATEGORY_IDE, + TooltipTag.PLUGIN_MANAGER_ITEM_MENU, + ) // Setup item click for details root.setOnClickListener { onActionClick(plugin, Action.DETAILS) } - // Long-press for Plugin Manager tooltip - root.setOnLongClickListener { - TooltipManager.showIdeCategoryTooltip(it.context, it, TooltipTag.PLUGIN_MANAGER_ITEM) - true - } + root.displayTooltipOnLongPress( + itemView.context, + root, + TooltipCategory.CATEGORY_IDE, + TooltipTag.PLUGIN_MANAGER_ITEM, + ) } } From 174096f897764e8b05fcd9c9b9f40e23954986d8 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 06:08:34 -0700 Subject: [PATCH 18/23] ADFA-5088: Don't run a tooltip lookup for an empty tag A choice-entry long-press whose own tag and dialog tag were both unset called TooltipManager with tag="" - not a crash (getTooltip finds no row and returns null), but a wasted coroutine/DB round trip and an Log.e for what isn't actually an error. Skip the call when the resolved tag is empty. Co-Authored-By: Claude Sonnet 5 --- .../androidide/preferences/ChoiceBasedDialogPreference.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt index a6c39a2369..8774c3d538 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt @@ -63,7 +63,9 @@ abstract class ChoiceBasedDialogPreference : ) { dialog.listView?.setOnItemLongClickListener { _, view, position, _ -> val tag = choices.getOrNull(position)?.tooltipTag?.takeIf { it.isNotEmpty() } ?: tooltipTag - TooltipManager.showIdeCategoryTooltip(preference.context, view, tag) + if (tag.isNotEmpty()) { + TooltipManager.showIdeCategoryTooltip(preference.context, view, tag) + } true } } From 9e948d95cdcc4215fb2bad03c41161599437c619 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 06:08:55 -0700 Subject: [PATCH 19/23] ADFA-5088: Link new Tooltips to their Content pages, drop banner comments - Neither script inserted any TooltipButtons row, so the ~72 new Content (Tier-3) pages were unreachable from any tooltip's popup - Tier 1/2 (summary/detail) worked, but the richer detail page never got a "Learn more" link. Add one idempotent TooltipButtons row per Tooltips/Content pair (delete-then-insert, since TooltipButtons has no unique constraint to upsert against), including for the 5 screen-level tags left alone in the Tooltips table but which still got a new Content page. The tag-to-path pairing was derived programmatically from each script's own INSERT statements and verified as an exact bijection (every tag maps to exactly one Content path and vice versa) before generating anything, rather than hand-matched. - Removed the `-- ---...---` banner-bar section comments: CLAUDE.md's Code style section bans decorative separators in code comments, and SQL's `--` is the direct analog of the rule's own `// ====` example. Re-validated end to end against a scratch copy of the real, pristine documentation.db.save: both scripts apply cleanly, every one of the 72 tag/path pairs links to exactly one TooltipButtons row, a second run is a no-op (idempotent), and Content still decompresses correctly. Co-Authored-By: Claude Sonnet 5 --- .../ADFA-5088-plugin-manager-tooltips.sql | 33 ++- docs/docdb/ADFA-5088-preference-tooltips.sql | 205 +++++++++++++++++- 2 files changed, 228 insertions(+), 10 deletions(-) diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql index 347deed5ef..15436f31f0 100644 --- a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -77,20 +77,16 @@ CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > .system rm -rf /tmp/adfa5088-pm-workdir .system mkdir -m 700 /tmp/adfa5088-pm-workdir --- --------------------------------------------------------------------- -- Remove the dead "plugin.manager" tag: no code path can reach it any -- more now that every widget has its own tag. Delete the TooltipButtons -- row first (it references Tooltips.id via a foreign key). --- --------------------------------------------------------------------- DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager' AND categoryId = 1); DELETE FROM Tooltips WHERE tag = 'plugin.manager' AND categoryId = 1; --- --------------------------------------------------------------------- -- Tooltips: idempotent upserts (all new tags) --- --------------------------------------------------------------------- INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.toolbar', 'Plugin Manager lists your installed plugins.', 'Use this screen to install a new plugin, open a plugin''s details, or find more plugins. The back button returns to Preferences.') ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; @@ -113,9 +109,7 @@ INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manag INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'plugin.manager.item.menu', 'More actions for this plugin.', 'Opens a menu with actions for this plugin, such as enable, disable, uninstall, or view details. Which actions appear depends on the plugin''s current state.') ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; --- --------------------------------------------------------------------- -- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. --- --------------------------------------------------------------------- .system rm -f /tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br .system echo "

Plugin Manager lists every plugin currently installed in Code on the Go.

From here you can install a new plugin from a file, find more plugins online, and open any installed plugin's details or actions.

" | brotli -Z > /tmp/adfa5088-pm-workdir/adfa5088-pm-toolbar.br @@ -152,5 +146,32 @@ INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/plugin/manager/item/menu', 1, 12, READFILE('/tmp/adfa5088-pm-workdir/adfa5088-pm-item-menu.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +-- Tier 3 links: without a TooltipButtons row, a tooltip's popup has no way to +-- surface its Content page - Tier 1 (summary) and Tier 2 (detail) still work +-- from the Tooltips row alone, but the richer Content page above is otherwise +-- unreachable. buttonNumberId 1 matches the existing single-button convention +-- (see e.g. the debugger-panel tooltip). Idempotent: delete then insert, since +-- TooltipButtons has no unique constraint to upsert against. + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.toolbar' AND categoryId = 1) AND uri = 'i/plugin/manager/toolbar'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.toolbar' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/toolbar'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.download' AND categoryId = 1) AND uri = 'i/plugin/manager/download'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.download' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/download'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.fab.install' AND categoryId = 1) AND uri = 'i/plugin/manager/fab/install'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.fab.install' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/fab/install'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.emptystate' AND categoryId = 1) AND uri = 'i/plugin/manager/emptystate'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.emptystate' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/emptystate'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.list' AND categoryId = 1) AND uri = 'i/plugin/manager/list'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.list' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/list'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item' AND categoryId = 1) AND uri = 'i/plugin/manager/item'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/item'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item.menu' AND categoryId = 1) AND uri = 'i/plugin/manager/item/menu'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'plugin.manager.item.menu' AND categoryId = 1), 1, 'Learn more', 'i/plugin/manager/item/menu'); .system rm -rf /tmp/adfa5088-pm-workdir COMMIT; diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql index f6fd7b6d05..d5e4321eac 100644 --- a/docs/docdb/ADFA-5088-preference-tooltips.sql +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -76,10 +76,8 @@ CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > .system rm -rf /tmp/adfa5088-prefs-workdir .system mkdir -m 700 /tmp/adfa5088-prefs-workdir --- --------------------------------------------------------------------- -- Tooltips: idempotent upserts (existing empty stub rows + brand new tags, -- all in one form so the script can be re-run safely) --- --------------------------------------------------------------------- INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.general.uimode', 'Choose light mode, dark mode, or match your device''s system setting.', 'This setting controls the color theme of the app. Pick Light, Dark, or Follow system. Follow system switches automatically when your device''s theme changes.') ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; @@ -261,11 +259,9 @@ INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.plugin INSERT INTO Tooltips (categoryId, tag, summary, detail) VALUES (1, 'prefs.about', 'See app version and other information about Code on the Go.', 'This opens the About screen, with details such as the app version and credits.') ON CONFLICT (categoryId, tag) DO UPDATE SET summary = excluded.summary, detail = excluded.detail; --- --------------------------------------------------------------------- -- Content: Tier 3 HTML pages, one INSERT per Tooltips row above. -- Each pair shows the uncompressed HTML via `.system echo ... | brotli -Z` -- then inserts the resulting compressed file with READFILE(). --- --------------------------------------------------------------------- .system rm -f /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br .system echo "

The General screen holds settings that are not specific to the editor or to a single project. Use it to set the app theme, choose a display language, and control how Code on the Go opens the last project on launch.

Changes here apply immediately and affect the whole app.

" | brotli -Z > /tmp/adfa5088-prefs-workdir/adfa5088-prefs-general.br @@ -592,5 +588,206 @@ INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/ INSERT INTO _content_guard SELECT READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br'); INSERT INTO Content (path, languageId, contentTypeId, content) VALUES ('i/prefs/devoptions/logsender', 1, 12, READFILE('/tmp/adfa5088-prefs-workdir/adfa5088-prefs-devoptions-logsender.br')) ON CONFLICT (path) DO UPDATE SET content = excluded.content; +-- Tier 3 links: without a TooltipButtons row, a tooltip's popup has no way to +-- surface its Content page - Tier 1 (summary) and Tier 2 (detail) still work +-- from the Tooltips row alone, but the richer Content page above is otherwise +-- unreachable. buttonNumberId 1 matches the existing single-button convention +-- (see e.g. the debugger-panel tooltip). Idempotent: delete then insert, since +-- TooltipButtons has no unique constraint to upsert against. + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.uimode' AND categoryId = 1) AND uri = 'i/prefs/general/uimode'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.uimode' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/uimode'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.language' AND categoryId = 1) AND uri = 'i/prefs/general/language'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.language' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/language'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.openlast' AND categoryId = 1) AND uri = 'i/prefs/general/openlast'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.openlast' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/openlast'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general.confirmopen' AND categoryId = 1) AND uri = 'i/prefs/general/confirmopen'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general.confirmopen' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general/confirmopen'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.fontsize' AND categoryId = 1) AND uri = 'i/prefs/editor/fontsize'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.fontsize' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/fontsize'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.tabsize' AND categoryId = 1) AND uri = 'i/prefs/editor/tabsize'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.tabsize' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/tabsize'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.softtab' AND categoryId = 1) AND uri = 'i/prefs/editor/softtab'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.softtab' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/softtab'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordwrap' AND categoryId = 1) AND uri = 'i/prefs/editor/wordwrap'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordwrap' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/wordwrap'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.magnifier' AND categoryId = 1) AND uri = 'i/prefs/editor/magnifier'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.magnifier' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/magnifier'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordboundaries' AND categoryId = 1) AND uri = 'i/prefs/editor/wordboundaries'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.wordboundaries' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/wordboundaries'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.matchcase' AND categoryId = 1) AND uri = 'i/prefs/editor/matchcase'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.matchcase' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/matchcase'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.deletelines' AND categoryId = 1) AND uri = 'i/prefs/editor/deletelines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.deletelines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/deletelines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.smartbackspace' AND categoryId = 1) AND uri = 'i/prefs/editor/smartbackspace'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.smartbackspace' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/smartbackspace'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.stickyscroll' AND categoryId = 1) AND uri = 'i/prefs/editor/stickyscroll'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.stickyscroll' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/stickyscroll'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.pinlines' AND categoryId = 1) AND uri = 'i/prefs/editor/pinlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.pinlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/pinlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.googlestyle' AND categoryId = 1) AND uri = 'i/prefs/editor/googlestyle'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.googlestyle' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/googlestyle'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun' AND categoryId = 1) AND uri = 'i/prefs/build'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.autolaunch' AND categoryId = 1) AND uri = 'i/prefs/build/autolaunch'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.autolaunch' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/autolaunch'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags' AND categoryId = 1) AND uri = 'i/prefs/build/flags'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.loglevel' AND categoryId = 1) AND uri = 'i/prefs/termux/loglevel'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.loglevel' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/loglevel'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.keylogging' AND categoryId = 1) AND uri = 'i/prefs/termux/keylogging'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.keylogging' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/keylogging'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.margin' AND categoryId = 1) AND uri = 'i/prefs/termux/margin'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.margin' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/margin'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.nohardkeyboard' AND categoryId = 1) AND uri = 'i/prefs/termux/nohardkeyboard'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.nohardkeyboard' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/nohardkeyboard'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.softkeyboard' AND categoryId = 1) AND uri = 'i/prefs/termux/softkeyboard'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.softkeyboard' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/softkeyboard'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.closebracket' AND categoryId = 1) AND uri = 'i/prefs/xml/closebracket'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.closebracket' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/closebracket'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.emptyelements' AND categoryId = 1) AND uri = 'i/prefs/xml/emptyelements'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.emptyelements' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/emptyelements'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.maxlinewidth' AND categoryId = 1) AND uri = 'i/prefs/xml/maxlinewidth'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.maxlinewidth' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/maxlinewidth'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveattributes' AND categoryId = 1) AND uri = 'i/prefs/xml/preserveattributes'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveattributes' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/preserveattributes'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preservenewlines' AND categoryId = 1) AND uri = 'i/prefs/xml/preservenewlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preservenewlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/preservenewlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.spacebeforeclose' AND categoryId = 1) AND uri = 'i/prefs/xml/spacebeforeclose'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.spacebeforeclose' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/spacebeforeclose'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattribindent' AND categoryId = 1) AND uri = 'i/prefs/xml/splitattribindent'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattribindent' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/splitattribindent'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimwhitespace' AND categoryId = 1) AND uri = 'i/prefs/xml/trimwhitespace'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimwhitespace' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/trimwhitespace'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions' AND categoryId = 1) AND uri = 'i/prefs/devoptions'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions' AND categoryId = 1), 1, 'Learn more', 'i/prefs/devoptions'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.dumplogs' AND categoryId = 1) AND uri = 'i/prefs/devoptions/dumplogs'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.dumplogs' AND categoryId = 1), 1, 'Learn more', 'i/prefs/devoptions/dumplogs'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.logsender' AND categoryId = 1) AND uri = 'i/prefs/devoptions/logsender'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.devoptions.logsender' AND categoryId = 1), 1, 'Learn more', 'i/prefs/devoptions/logsender'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.leading' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/leading'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.leading' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/leading'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.trailing' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/trailing'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.trailing' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/trailing'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.inner' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/inner'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.inner' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/inner'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.emptylines' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/emptylines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.emptylines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/emptylines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.linebreaks' AND categoryId = 1) AND uri = 'i/prefs/editor/nonprinting/linebreaks'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.nonprinting.linebreaks' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/nonprinting/linebreaks'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimfinalnewline' AND categoryId = 1) AND uri = 'i/prefs/xml/trimfinalnewline'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.trimfinalnewline' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/trimfinalnewline'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.insertfinalnewline' AND categoryId = 1) AND uri = 'i/prefs/xml/insertfinalnewline'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.insertfinalnewline' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/insertfinalnewline'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattributes' AND categoryId = 1) AND uri = 'i/prefs/xml/splitattributes'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.splitattributes' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/splitattributes'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincdatalines' AND categoryId = 1) AND uri = 'i/prefs/xml/joincdatalines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincdatalines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/joincdatalines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincommentlines' AND categoryId = 1) AND uri = 'i/prefs/xml/joincommentlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincommentlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/joincommentlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincontentlines' AND categoryId = 1) AND uri = 'i/prefs/xml/joincontentlines'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.joincontentlines' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/joincontentlines'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveemptycontent' AND categoryId = 1) AND uri = 'i/prefs/xml/preserveemptycontent'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.xml.preserveemptycontent' AND categoryId = 1), 1, 'Learn more', 'i/prefs/xml/preserveemptycontent'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.stacktrace' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--stacktrace'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.stacktrace' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--stacktrace'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.info' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--info'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.info' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--info'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.debug' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--debug'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.debug' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--debug'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.scan' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--scan'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.scan' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--scan'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.warningmodeall' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--warning-mode-all'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.warningmodeall' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--warning-mode-all'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.buildcache' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--build-cache'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.buildcache' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--build-cache'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.offline' AND categoryId = 1) AND uri = 'i/prefs/build/flags/--offline'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.buildrun.flags.offline' AND categoryId = 1), 1, 'Learn more', 'i/prefs/build/flags/--offline'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux.crashreports' AND categoryId = 1) AND uri = 'i/prefs/termux/crashreports'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux.crashreports' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux/crashreports'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.git.username' AND categoryId = 1) AND uri = 'i/prefs/git/username'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.git.username' AND categoryId = 1), 1, 'Learn more', 'i/prefs/git/username'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.git.useremail' AND categoryId = 1) AND uri = 'i/prefs/git/useremail'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.git.useremail' AND categoryId = 1), 1, 'Learn more', 'i/prefs/git/useremail'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.pluginmanager' AND categoryId = 1) AND uri = 'i/prefs/pluginmanager'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.pluginmanager' AND categoryId = 1), 1, 'Learn more', 'i/prefs/pluginmanager'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.about' AND categoryId = 1) AND uri = 'i/prefs/about'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.about' AND categoryId = 1), 1, 'Learn more', 'i/prefs/about'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general' AND categoryId = 1) AND uri = 'i/prefs/general'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor' AND categoryId = 1) AND uri = 'i/prefs/editor'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.editor.xml' AND categoryId = 1) AND uri = 'i/prefs/editor/xml'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.editor.xml' AND categoryId = 1), 1, 'Learn more', 'i/prefs/editor/xml'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.termux' AND categoryId = 1) AND uri = 'i/prefs/termux'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.termux' AND categoryId = 1), 1, 'Learn more', 'i/prefs/termux'); + +DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.git' AND categoryId = 1) AND uri = 'i/prefs/git'; +INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.git' AND categoryId = 1), 1, 'Learn more', 'i/prefs/git'); .system rm -rf /tmp/adfa5088-prefs-workdir COMMIT; From 28dc81a6a1d385ef4580e14be7d1e2576689a25e Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 12:44:42 -0700 Subject: [PATCH 20/23] ADFA-5088: Guard the SQL scripts' workdir, document getProperties() - Both docdb scripts' mkdir -m 700 can fail without .bail seeing it (a .system failure, not a SQL one) - e.g. another process recreates the workdir path between the rm -rf and the mkdir. Assert the directory's actual mode is 700 before trusting it with any Content writes, using the same guard-table trick _content_guard already uses for the Brotli payloads: stat the directory, READFILE the result back into a TEMP table with a CHECK constraint, so a mismatch is a real SQL error .bail does catch. (First attempt compared the CAST-less BLOB from READFILE() against a TEXT literal, which SQLite never treats as equal regardless of content - caught by re-running the script rather than assuming the happy path.) - getProperties() had no KDoc despite being the seam a screen author actually has to implement: document that entry order is choice-list order and that each entry's own tooltipTag drives its long-press help. Re-validated both scripts end-to-end against a scratch copy of the real, pristine documentation.db.save: apply cleanly, a second run is a no-op, Content still decompresses, and a hand-built repro confirms the new guard trips (real SQL error, non-zero exit) when the workdir's mode isn't actually 700. Co-Authored-By: Claude Sonnet 5 --- .../com/itsaky/androidide/preferences/commonPrefExts.kt | 1 + docs/docdb/ADFA-5088-plugin-manager-tooltips.sql | 9 ++++++++- docs/docdb/ADFA-5088-preference-tooltips.sql | 9 ++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt index 8c67778d38..bd4dbd11f1 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt @@ -30,6 +30,7 @@ data class PropertyEntry( val tooltipTag: String = "", ) +/** The checkboxes for this screen, in display order; each entry's [PropertyEntry.tooltipTag] is that choice's own long-press help. */ abstract fun getProperties(): List override fun getEntries(preference: Preference): Array { diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql index 15436f31f0..00536d7e8f 100644 --- a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -73,9 +73,16 @@ CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > -- symlink or race the write/read pair. mkdir -m sets the mode atomically -- at creation (no separate chmod, no window with a wider mode); the prior -- rm -rf makes each run start from a clean directory it fully owns, --- rather than trusting one left over from an earlier run. +-- rather than trusting one left over from an earlier run. mkdir itself +-- can fail without .bail seeing it (e.g. another process recreates the +-- path between the rm -rf and the mkdir), so assert the mode really is +-- 700 before trusting the directory with anything - the same guard-table +-- trick _content_guard uses for the Brotli payloads below. .system rm -rf /tmp/adfa5088-pm-workdir .system mkdir -m 700 /tmp/adfa5088-pm-workdir +.system stat --printf='%a' /tmp/adfa5088-pm-workdir > /tmp/adfa5088-pm-workdir/.mode +CREATE TEMP TABLE _workdir_guard (mode TEXT NOT NULL CHECK (mode = '700')); +INSERT INTO _workdir_guard SELECT CAST(READFILE('/tmp/adfa5088-pm-workdir/.mode') AS TEXT); -- Remove the dead "plugin.manager" tag: no code path can reach it any -- more now that every widget has its own tag. Delete the TooltipButtons diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql index d5e4321eac..1690ced7e4 100644 --- a/docs/docdb/ADFA-5088-preference-tooltips.sql +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -72,9 +72,16 @@ CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > -- symlink or race the write/read pair. mkdir -m sets the mode atomically -- at creation (no separate chmod, no window with a wider mode); the prior -- rm -rf makes each run start from a clean directory it fully owns, --- rather than trusting one left over from an earlier run. +-- rather than trusting one left over from an earlier run. mkdir itself +-- can fail without .bail seeing it (e.g. another process recreates the +-- path between the rm -rf and the mkdir), so assert the mode really is +-- 700 before trusting the directory with anything - the same guard-table +-- trick _content_guard uses for the Brotli payloads below. .system rm -rf /tmp/adfa5088-prefs-workdir .system mkdir -m 700 /tmp/adfa5088-prefs-workdir +.system stat --printf='%a' /tmp/adfa5088-prefs-workdir > /tmp/adfa5088-prefs-workdir/.mode +CREATE TEMP TABLE _workdir_guard (mode TEXT NOT NULL CHECK (mode = '700')); +INSERT INTO _workdir_guard SELECT CAST(READFILE('/tmp/adfa5088-prefs-workdir/.mode') AS TEXT); -- Tooltips: idempotent upserts (existing empty stub rows + brand new tags, -- all in one form so the script can be re-run safely) From 7f5232b1feaeab1d81d3ef9be1391d24cecce78b Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 20:50:46 -0700 Subject: [PATCH 21/23] ADFA-5088: Fix long-press tooltip gaps found by /code-review xhigh - PreferencesActivity's toolbar always showed the generic PREFS_TOP tag regardless of the screen actually on display, since the old dynamic lookup (via the deleted screen-wide GestureDetector) was replaced with a hardcoded constant. Resolve it from the current top fragment's own screenTooltipTag instead, falling back to PREFS_TOP only when no IDEPreferencesFragment is showing yet. - A long-press below a short screen's content (Git, Developer Options, About) hit nothing: the RecyclerView is wrap_content-sized to its own rows, so on a short screen it doesn't fill the surrounding NestedScrollView, and that remaining space had no long-press listener at all after the old screen-wide GestureDetector was removed. Give the NestedScrollView its own listener using the same current-screen-tag lookup as the toolbar. - collectTooltipTags silently let a later duplicate preference key overwrite an earlier one's tag - the exact "silent drift on collision" pattern this PR's own commits eliminated elsewhere. Fail fast with a clear message instead; no existing screen has a collision, so this is a no-op today and only fires on a future copy-paste bug. - DialogPreference's base long-press handler had no isNotEmpty() guard on tooltipTag, unlike the equivalent guard this PR added at the choice-list call site - harmless today since every concrete subclass sets a real tag, but inconsistent with the sibling fix. - Removed 10 tooltipTag overrides on category/group classes (GradleOptions, RunOptions, CommonConfigurations, JavaCodeConfigurations, GitAuthorConfig, TermuxDebuggingPreferences, TermuxKeyboardPreferences, TermuxViewPreferences, InterfaceConfig, ProjectConfig) that just repeated their enclosing screen's own tag - provably a no-op, since resolveTooltipTag already falls back to screenTooltipTag for any row with an empty tag. Left every case where a nested IPreferenceScreen sets the same tag as its parent (e.g. XMLFormattingOptions) alone: those aren't no-ops, since a nested screen's tag flows through EXTRA_SCREEN_TOOLTIP_TAG explicitly, with no automatic fallback to the parent's tag if omitted. - children no longer needs to be a fragment field: its only other reader (the old getCurrentScreenTooltip()) was deleted by this PR, so it's now a local val in onCreatePreferences. - Made resolveTooltipTag internal and added real RecyclerView + PreferenceGroupAdapter-backed tests for it - the AndroidX-internals- dependent logic that only had zero coverage before, unlike collectTooltipTags. - SQL: the "Deliberately NOT included" list named 5 pre-existing curated tags but missed a 6th (prefs.top, the toolbar's tag) that's equally untouched by this script. Also corrected an overclaim that the workdir is "removed at the end" - true only for a successful run; a `.bail` abort skips that line entirely, so a failed run's leftovers are only cleaned up by the *next* run's leading rm -rf, not immediately. Noted the two scripts' fixed-path workdirs aren't safe to run concurrently. Skipped as not worth fixing here (kept minimal per review protocol): - Deduplicating the two SQL scripts' shared boilerplate via sqlite3's `.read`: it resolves relative to the CLI's cwd, not the script's own directory, which would silently break for anyone who doesn't cd into docs/docdb first - not documented anywhere today, and not worth the footgun for two files. - The double READFILE() per Content row (once for the guard, once for the real insert): the review's own finding calls this low-impact (KB-sized files, 72 rows total); a real fix needs a more fragile pattern than the I/O savings justify. - Making IPreference.tooltipTag abstract instead of defaulting to "": every existing IPreference subclass across the app would need an explicit override, reversing a default this same PR deliberately introduced - a bigger design change than a review-response fix. - Sharing an abstraction between ChoiceBasedDialogPreference's ListView-position resolution and IDEPreferencesFragment's RecyclerView-adapter-position resolution: they operate on structurally different widgets: forcing a shared type would add indirection without removing the real duplication. - ToolTipManager opening a fresh CoroutineScope/SQLiteDatabase connection per call: pre-existing, predates this PR, and is out of scope for a tooltip-tag review response. Verified: :app and :preferences compile, the full :app and :preferences unit test suites pass (7 tests in IDEPreferencesFragmentTest, all green), spotlessApply is a no-op, and both docdb SQL scripts re-applied end-to-end against a scratch copy of documentation.db.save (apply, idempotent re-run, both scripts) with no errors. Co-Authored-By: Claude Sonnet 5 --- .../activities/PreferencesActivity.kt | 20 +++++- .../fragments/IDEPreferencesFragment.kt | 20 +++--- .../preferences/buildAndRunPrefExts.kt | 2 - .../androidide/preferences/editorPrefExts.kt | 1 - .../androidide/preferences/generalPrefExts.kt | 2 - .../androidide/preferences/gitPrefExts.kt | 1 - .../androidide/preferences/javaPrefExts.kt | 1 - .../androidide/preferences/termuxPrefsExt.kt | 3 - .../fragments/IDEPreferencesFragmentTest.kt | 63 +++++++++++++++++++ .../ADFA-5088-plugin-manager-tooltips.sql | 15 +++-- docs/docdb/ADFA-5088-preference-tooltips.sql | 26 ++++---- .../preferences/DialogPreference.kt | 4 +- 12 files changed, 120 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt index 044add0dc8..12ea83e25c 100755 --- a/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/PreferencesActivity.kt @@ -57,7 +57,19 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { binding.toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() } binding.toolbar.setOnLongClickListener { - TooltipManager.showIdeCategoryTooltip(this, binding.toolbar, TooltipTag.PREFS_TOP) + TooltipManager.showIdeCategoryTooltip(this, binding.toolbar, currentScreenTooltipTag()) + true + } + + // Fallback for a long-press that lands below a short screen's content: the RecyclerView is + // wrap_content-sized to its own rows, so on a short screen (e.g. Git, About) it doesn't fill + // this NestedScrollView, and the remaining space would otherwise have no tooltip at all. + binding.fragmentContainerParent.setOnLongClickListener { + TooltipManager.showIdeCategoryTooltip( + this, + binding.fragmentContainerParent, + currentScreenTooltipTag(), + ) true } @@ -146,6 +158,12 @@ class PreferencesActivity : EdgeToEdgeIDEActivity() { super.loadFragment(fragment, binding.fragmentContainer.id) } + /** The tag of whichever [IDEPreferencesFragment] screen is currently on top, or [TooltipTag.PREFS_TOP]. */ + private fun currentScreenTooltipTag(): String { + val fragment = supportFragmentManager.findFragmentById(binding.fragmentContainer.id) + return (fragment as? IDEPreferencesFragment)?.screenTooltipTag ?: TooltipTag.PREFS_TOP + } + override fun onDestroy() { super.onDestroy() _binding = null diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt index 20fdc1644e..1510ec7660 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt @@ -35,16 +35,17 @@ import com.itsaky.androidide.preferences.IPreferenceScreen import com.itsaky.androidide.utils.onLongPress class IDEPreferencesFragment : BasePreferenceFragment() { - private var children: List = emptyList() - /** Every preference in this screen, including nested categories' children, keyed by its key. */ - private var tooltipTagsByKey: Map = emptyMap() + internal var tooltipTagsByKey: Map = emptyMap() /** * This screen's own tag - the fallback for a long-press that lands on empty RecyclerView - * space (no row under the touch point) or on a row with no tooltipTag of its own. + * space (no row under the touch point) or on a row with no tooltipTag of its own. Also read + * by [com.itsaky.androidide.activities.PreferencesActivity] to resolve the toolbar's and the + * scroll container's long-press tooltip to whichever screen is currently showing. */ - private var screenTooltipTag: String = PREFS_TOP + internal var screenTooltipTag: String = PREFS_TOP + private set override fun onCreateView( inflater: LayoutInflater, @@ -68,12 +69,12 @@ class IDEPreferencesFragment : BasePreferenceFragment() { } @Suppress("DEPRECATION") - this.children = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() - this.tooltipTagsByKey = collectTooltipTags(this.children) + val children: List = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() + this.tooltipTagsByKey = collectTooltipTags(children) this.screenTooltipTag = arguments?.getString(EXTRA_SCREEN_TOOLTIP_TAG)?.takeIf { it.isNotEmpty() } ?: PREFS_TOP preferenceScreen.removeAll() - addChildren(this.children, preferenceScreen) + addChildren(children, preferenceScreen) } override fun onViewCreated( @@ -102,7 +103,7 @@ class IDEPreferencesFragment : BasePreferenceFragment() { } /** The row's own tooltipTag, or null if there's no row at that position or it has none. */ - private fun resolveTooltipTag( + internal fun resolveTooltipTag( recyclerView: RecyclerView, row: View, ): String? { @@ -120,6 +121,7 @@ class IDEPreferencesFragment : BasePreferenceFragment() { fun visit(items: List) { for (item in items) { + check(item.key !in map) { "Duplicate preference key in this screen's tree: ${item.key}" } map[item.key] = item.tooltipTag if (item is IPreferenceGroup && item !is IPreferenceScreen) { visit(item.children) diff --git a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt index 3357ce9b8d..0d2db90372 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt @@ -63,7 +63,6 @@ private class GradleOptions( override val key: String = "idepref_build_gradle", override val title: Int = string.gradle, override val children: List = mutableListOf(), - override val tooltipTag: String = PREFS_BUILD_RUN, ) : IPreferenceGroup() { init { @@ -101,7 +100,6 @@ private class RunOptions( override val key: String = "ide.build.runOptions", override val title: Int = R.string.title_run_options, override val children: List = mutableListOf(), - override val tooltipTag: String = PREFS_BUILD_RUN, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt index be8640210c..1d6c3d157c 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt @@ -62,7 +62,6 @@ private class CommonConfigurations( override val key: String = "idepref_editor_common", override val title: Int = string.idepref_editor_category_common, override val children: List = mutableListOf(), -override val tooltipTag: String = TooltipTag.PREFS_EDITOR, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt index 5db309fd0b..83a30fadb4 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt @@ -54,7 +54,6 @@ class InterfaceConfig( override val key: String = "idepref_general_interface", override val title: Int = string.title_interface, override val children: List = mutableListOf(), -override val tooltipTag: String = PREFS_GENERAL, ) : IPreferenceGroup() { init { @@ -68,7 +67,6 @@ class ProjectConfig( override val key: String = "idepref_general_project", override val title: Int = R.string.idepref_general_projectConfig, override val children: List = mutableListOf(), -override val tooltipTag: String = PREFS_GENERAL, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt index c9fd6459f2..9e0d102823 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/gitPrefExts.kt @@ -29,7 +29,6 @@ class GitAuthorConfig( override val title: Int = R.string.idepref_git_author_title, override val summary: Int? = R.string.idepref_git_author_summary, override val children: List = mutableListOf(), - override val tooltipTag: String = PREFS_GIT, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt index 783152f467..44197b1985 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/javaPrefExts.kt @@ -29,7 +29,6 @@ internal class JavaCodeConfigurations( override val key: String = "idepref_editor_java", override val title: Int = string.idepref_editor_category_java, override val children: List = mutableListOf(), -override val tooltipTag: String = TooltipTag.PREFS_EDITOR, ) : IPreferenceGroup() { init { diff --git a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt index 320bc38e36..8aba76f2c8 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt @@ -98,7 +98,6 @@ class TermuxDebuggingPreferences( override val key: String = KEY_TERMUX_DEBUGGING_PREFERENCES, override val title: Int = R.string.termux_debugging_preferences_title, override val children: List = mutableListOf(), -override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceGroup() { init { @@ -195,7 +194,6 @@ class TermuxKeyboardPreferences( override val key: String = KEY_TERMUX_KBD_PREFERENCES, override val title: Int = R.string.termux_keyboard_header, override val children: List = mutableListOf(), -override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceGroup() { init { @@ -249,7 +247,6 @@ class TermuxViewPreferences( override val key: String = KEY_TERMUX_VIEW_PREFERENCES, override val title: Int = R.string.termux_terminal_view_view_header, override val children: List = mutableListOf(), -override val tooltipTag: String = PREFS_TERMUX, ) : IPreferenceGroup() { init { diff --git a/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt index 8474681ace..e4d2afe922 100644 --- a/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt +++ b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt @@ -1,13 +1,20 @@ package com.itsaky.androidide.fragments import android.content.Context +import android.view.View import androidx.preference.Preference +import androidx.preference.PreferenceGroupAdapter +import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import com.itsaky.androidide.preferences.IPreference import com.itsaky.androidide.preferences.IPreferenceGroup import com.itsaky.androidide.preferences.IPreferenceScreen import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize +import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -65,6 +72,62 @@ class IDEPreferencesFragmentTest { assertThat(tags).containsExactly("untagged", "") } + + @Test + fun `collectTooltipTags rejects a duplicate key instead of silently overwriting its tag`() { + val children = + listOf( + FakeItem(key = "dup", tooltipTag = "tag.first"), + FakeItem(key = "dup", tooltipTag = "tag.second"), + ) + + assertThrows(IllegalStateException::class.java) { + IDEPreferencesFragment().collectTooltipTags(children) + } + } + + @Test + fun `resolveTooltipTag looks up the real adapter position's own tag`() { + val recyclerView = buildRecyclerView(keys = listOf("a", "b")) + val fragment = + IDEPreferencesFragment().apply { + tooltipTagsByKey = mapOf("a" to "tag.a", "b" to "tag.b") + } + + val rowB = recyclerView.getChildAt(1) + + assertThat(fragment.resolveTooltipTag(recyclerView, rowB)).isEqualTo("tag.b") + } + + @Test + fun `resolveTooltipTag returns null for a row whose own tag is empty`() { + val recyclerView = buildRecyclerView(keys = listOf("a")) + val fragment = + IDEPreferencesFragment().apply { + tooltipTagsByKey = mapOf("a" to "") + } + + val rowA = recyclerView.getChildAt(0) + + assertThat(fragment.resolveTooltipTag(recyclerView, rowA)).isNull() + } + + /** A real, laid-out RecyclerView backed by a real PreferenceGroupAdapter - not a fake. */ + private fun buildRecyclerView(keys: List): RecyclerView { + val context = ApplicationProvider.getApplicationContext() + val screen = PreferenceManager(context).createPreferenceScreen(context) + keys.forEach { screen.addPreference(Preference(context).apply { key = it }) } + + return RecyclerView(context).apply { + layoutManager = LinearLayoutManager(context) + adapter = PreferenceGroupAdapter(screen) + measure( + View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), + ) + layout(0, 0, 1000, 1000) + } + } } @Parcelize diff --git a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql index 00536d7e8f..f59b60a089 100644 --- a/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql +++ b/docs/docdb/ADFA-5088-plugin-manager-tooltips.sql @@ -30,12 +30,15 @@ -- -- Every Brotli payload is written under /tmp/adfa5088-pm-workdir, an -- owner-only (mode 700) directory this script creates fresh and removes --- at the end - not bare /tmp filenames, which are guessable and world- --- writable, so another local user could pre-plant a symlink or race the --- write/read pair (CWE-377). `mkdir -m` sets the mode atomically at --- creation, and the preceding `rm -rf` means each run starts from a --- directory it fully owns rather than trusting one left over from an --- earlier run. +-- again at the end of a successful run - not bare /tmp filenames, which +-- are guessable and world-writable, so another local user could pre-plant +-- a symlink or race the write/read pair (CWE-377). `mkdir -m` sets the +-- mode atomically at creation, and the preceding `rm -rf` means each run +-- starts from a directory it fully owns rather than trusting one left +-- over from an earlier run - including one left behind by a `.bail` +-- abort part-way through a previous run, since that skips the cleanup at +-- the end. Don't run two copies of this script at once against the same +-- database: both would share this one fixed workdir path. -- -- For each Content row: `.system rm -f /x.br` clears any stale -- file, `.system echo "" | brotli -Z > /x.br` writes the diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql index 1690ced7e4..721d7ad4a5 100644 --- a/docs/docdb/ADFA-5088-preference-tooltips.sql +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -6,11 +6,12 @@ -- (Tier 1 `summary` + Tier 2 `detail`) and a matching Content row (Tier 3 -- HTML page) at a new, item-granular `i/prefs/...` path. -- --- Deliberately NOT included: prefs.general, prefs.editor, prefs.editor.xml, --- prefs.termux, and prefs.git already exist in the real documentation.db --- with good, real (non-empty) summary/detail text -- these tags are reused --- as-is for the corresponding screen row, so this script leaves them alone --- rather than overwriting curated production content with a draft. +-- Deliberately NOT included: prefs.top, prefs.general, prefs.editor, +-- prefs.editor.xml, prefs.termux, and prefs.git already exist in the real +-- documentation.db with good, real (non-empty) summary/detail text -- these +-- tags are reused as-is for the corresponding screen/toolbar row, so this +-- script leaves them alone rather than overwriting curated production +-- content with a draft. -- -- Both Tooltips (UNIQUE(categoryId, tag)) and Content (UNIQUE(path)) are -- idempotent `INSERT ... ON CONFLICT ... DO UPDATE` upserts below, so the @@ -29,12 +30,15 @@ -- -- Every Brotli payload is written under /tmp/adfa5088-prefs-workdir, an -- owner-only (mode 700) directory this script creates fresh and removes --- at the end - not bare /tmp filenames, which are guessable and world- --- writable, so another local user could pre-plant a symlink or race the --- write/read pair (CWE-377). `mkdir -m` sets the mode atomically at --- creation, and the preceding `rm -rf` means each run starts from a --- directory it fully owns rather than trusting one left over from an --- earlier run. +-- again at the end of a successful run - not bare /tmp filenames, which +-- are guessable and world-writable, so another local user could pre-plant +-- a symlink or race the write/read pair (CWE-377). `mkdir -m` sets the +-- mode atomically at creation, and the preceding `rm -rf` means each run +-- starts from a directory it fully owns rather than trusting one left +-- over from an earlier run - including one left behind by a `.bail` +-- abort part-way through a previous run, since that skips the cleanup at +-- the end. Don't run two copies of this script at once against the same +-- database: both would share this one fixed workdir path. -- -- For each Content row: `.system rm -f /x.br` clears any stale -- file, `.system echo "" | brotli -Z > /x.br` writes the diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt index 5b594c1fed..b1b7aceb3e 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/DialogPreference.kt @@ -47,7 +47,9 @@ abstract class DialogPreference : SimplePreference() { alertDialog.show() alertDialog.window?.decorView?.applyLongPressRecursively { - TooltipManager.showIdeCategoryTooltip(preference.context, it, tooltipTag) + if (tooltipTag.isNotEmpty()) { + TooltipManager.showIdeCategoryTooltip(preference.context, it, tooltipTag) + } true } From 6831991a3cb69ab3bc7ec0da39f6e713d3362846 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 13 Aug 2026 21:46:51 -0700 Subject: [PATCH 22/23] ADFA-5088: Add KDoc to collectTooltipTags Documents the recursion, IPreferenceScreen exclusion, empty-tag retention, and duplicate-key failure per code review feedback. Co-Authored-By: Claude Sonnet 5 --- .../itsaky/androidide/fragments/IDEPreferencesFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt index 1510ec7660..f64e7764f2 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt @@ -116,6 +116,11 @@ class IDEPreferencesFragment : BasePreferenceFragment() { return tooltipTagsByKey[key]?.takeIf { it.isNotEmpty() } } + /** + * Recursively walks [children], mapping each preference key to its tooltip tag. + * Nested [IPreferenceScreen] nodes are not descended into. Entries with an empty + * tooltip tag are kept as-is. Throws if two preferences in the tree share a key. + */ internal fun collectTooltipTags(children: List): Map { val map = mutableMapOf() From ab5436c5ed99af4ca95b901d4a9d78fa3ade8ace Mon Sep 17 00:00:00 2001 From: David Schachter Date: Fri, 14 Aug 2026 11:58:29 -0700 Subject: [PATCH 23/23] ADFA-5088: Fix second-round /code-review xhigh findings - collectTooltipTags()'s duplicate-key check() I added last round is reachable from untrusted plugin data, not just first-party bugs: two plugins (or one buggy plugin) contributing settings entries with the same id would crash the whole Preferences screen. Dedupe at the plugin boundary instead - pluginSettingsPreferences() now drops a later duplicate key (logging a warning) before it ever reaches the tree, so the check() stays a first-party-only invariant. - The preference-tooltips SQL script never deleted the two tags this same PR made permanently dead in Kotlin (prefs.gradle, prefs.developer), unlike the sibling plugin-manager script's explicit cleanup of its own dead tag. Added the same DELETE pair. - The TooltipButtons block for 5 pre-existing tags (prefs.general, prefs.editor, prefs.editor.xml, prefs.termux, prefs.git) assumed they already exist with no guard - since TooltipButtons.tooltipId has neither NOT NULL nor enforced FK (SQLite FKs are off by default), a wrong assumption would silently insert a dead NULL-tooltipId row instead of failing loudly. Added the same guard-table assertion pattern already used for the Brotli payloads and the workdir mode. - PluginListAdapter re-registered two OnLongClickListeners on every RecyclerView bind() even though both tags and anchor views are fixed per view holder - moved to init {} so it happens once per holder. - Deleted PropertyEntry: it duplicated PreferenceChoices.Entry's shape purely to get converted into one in getEntries(). getProperties() now returns List directly, built via a small propertyEntry(label, property, tag) helper that keeps call sites as concise as before. - Deleted DebuggingPreferences: confirmed still unreferenced anywhere after this PR touched every sibling class in the same file. Also removed the string resource that only it referenced, across all 13 locales (same cleanup ADFA-5121 already did for a similar orphaned string). - Moved tooltipTag into the primary constructor for GradleCommands, UiMode, LocaleSelector, and TermuxDebuggingLogLevelPreference, which used an @IgnoredOnParcel body override instead of the constructor default pattern ~146 of the ~150 other leaf preferences in this PR use. Purely cosmetic; dropped the now-unused IgnoredOnParcel imports. - Extracted the screenTooltipTag fallback (EXTRA_SCREEN_TOOLTIP_TAG, missing/blank -> PREFS_TOP) into resolveScreenTooltipTag(), an internal pure function, and added tests for it - the same pattern already used for resolveTooltipTag/collectTooltipTags, rather than standing up fragment-lifecycle test infra (androidx.fragment: fragment-testing isn't a dependency here, and shouldn't become one just for this) to exercise the same one-line default indirectly. Verified as invalid, not applied: - "XMLPreferencesScreen and XMLFormattingOptions share a tag, remove it from XMLPreferencesScreen" - traced this and it's the opposite of a no-op. XMLPreferencesScreen's tag matches its CHILD screen's tag, not its PARENT's (unlike GradleOptions/GitAuthorConfig, fixed last round, which matched their parent). Removing it would fall back to the enclosing EditorPreferencesScreen's generic tag, not to XMLFormattingOptions' tag - a real regression, not a cleanup. Skipped as not worth fixing here (kept minimal per review protocol): - ToolTipManager's per-call DB query + CoroutineScope allocation: pre-existing, predates this PR, out of scope for a tooltip-tag review response (same call as last round's equivalent finding). - Adding app:fillViewport="true" to drop the redundant fragmentContainerParent long-click listener: doesn't work as a one-line change (fillViewport only stretches a direct child declared match_parent height, and the LinearLayout here is wrap_content) - making it work risks BottomInsetHeightDistributor's documented "last view in the scrolling pane" contract, a system-bar-inset concern CLAUDE.md protects. Not worth that risk for two duplicated one-line listener bodies. - Generalizing applyLongPressRecursively/AlertDialog.onLongPress to absorb ChoiceBasedDialogPreference's ListView per-row long-click: the "ListView needs its own long-click listener since applyLongPressRecursively skips it" pattern already exists 4 times elsewhere in the codebase (FieldBasedAction, OverrideSuperclassMethodsAction, TemplateWidgetViewProviderImpl, EditorCompletionWindow), none touched by this PR - ChoiceBasedDialog- Preference matches an established convention rather than introducing a new one, and deduplicating it means refactoring unrelated files. - Sharing a traversal helper between collectTooltipTags's `is IPreferenceGroup && !is IPreferenceScreen` check and addChildren's own is-checks: same boolean condition, but each branch does structurally different work (map mutation vs. real UI construction + navigation-argument wiring, in a different branch order) - a shared callback-based traversal wouldn't meaningfully simplify either side. Verified: :app compiles, the full :app unit test suite passes (10 tests in IDEPreferencesFragmentTest, all green), spotlessApply is a no-op beyond what's in this diff, and both docdb SQL scripts re-applied end-to-end against a scratch copy of documentation.db.save (apply, idempotent re-run, both scripts, plus confirmed prefs.gradle/ prefs.developer are gone after the run). Co-Authored-By: Claude Sonnet 5 --- .../androidide/adapters/PluginListAdapter.kt | 30 +++++++++++-------- .../fragments/IDEPreferencesFragment.kt | 5 +++- .../preferences/buildAndRunPrefExts.kt | 21 ++++++------- .../androidide/preferences/commonPrefExts.kt | 25 +++++++--------- .../preferences/developerOptionsPrefExts.kt | 12 -------- .../androidide/preferences/editorPrefExts.kt | 12 ++++---- .../androidide/preferences/generalPrefExts.kt | 13 +++----- .../androidide/preferences/pluginPrefExts.kt | 22 ++++++++++++-- .../androidide/preferences/termuxPrefsExt.kt | 7 ++--- .../fragments/IDEPreferencesFragmentTest.kt | 16 ++++++++++ docs/docdb/ADFA-5088-preference-tooltips.sql | 21 +++++++++++++ .../src/main/res/values-ar-rSA/strings.xml | 1 - .../src/main/res/values-bn-rIN/strings.xml | 1 - .../src/main/res/values-de-rDE/strings.xml | 1 - .../src/main/res/values-es-rES/strings.xml | 1 - .../src/main/res/values-fr-rFR/strings.xml | 1 - .../src/main/res/values-hi-rIN/strings.xml | 1 - .../src/main/res/values-in-rID/strings.xml | 1 - .../src/main/res/values-pt-rBR/strings.xml | 1 - .../src/main/res/values-ro-rRO/strings.xml | 1 - .../src/main/res/values-ru-rRU/strings.xml | 1 - .../src/main/res/values-tr-rTR/strings.xml | 1 - .../src/main/res/values-zh-rCN/strings.xml | 1 - resources/src/main/res/values/strings.xml | 1 - 24 files changed, 108 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt index cfd6a9bfd0..cd5ee733c6 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/PluginListAdapter.kt @@ -56,6 +56,23 @@ class PluginListAdapter( inner class PluginViewHolder( private val binding: ItemPluginBinding, ) : RecyclerView.ViewHolder(binding.root) { + init { + // Both the anchor views and their tags are fixed per view holder, not per bound plugin - + // register once here instead of re-registering an identical listener on every bind(). + binding.btnMenu.displayTooltipOnLongPress( + itemView.context, + binding.btnMenu, + TooltipCategory.CATEGORY_IDE, + TooltipTag.PLUGIN_MANAGER_ITEM_MENU, + ) + binding.root.displayTooltipOnLongPress( + itemView.context, + binding.root, + TooltipCategory.CATEGORY_IDE, + TooltipTag.PLUGIN_MANAGER_ITEM, + ) + } + fun bind(plugin: PluginInfo) { binding.apply { pluginName.text = plugin.metadata.name @@ -116,24 +133,11 @@ class PluginListAdapter( btnMenu.setOnClickListener { view -> showPopupMenu(view, plugin) } - btnMenu.displayTooltipOnLongPress( - itemView.context, - btnMenu, - TooltipCategory.CATEGORY_IDE, - TooltipTag.PLUGIN_MANAGER_ITEM_MENU, - ) // Setup item click for details root.setOnClickListener { onActionClick(plugin, Action.DETAILS) } - - root.displayTooltipOnLongPress( - itemView.context, - root, - TooltipCategory.CATEGORY_IDE, - TooltipTag.PLUGIN_MANAGER_ITEM, - ) } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt index f64e7764f2..31115dce93 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDEPreferencesFragment.kt @@ -71,7 +71,7 @@ class IDEPreferencesFragment : BasePreferenceFragment() { @Suppress("DEPRECATION") val children: List = arguments?.getParcelableArrayList(EXTRA_CHILDREN) ?: emptyList() this.tooltipTagsByKey = collectTooltipTags(children) - this.screenTooltipTag = arguments?.getString(EXTRA_SCREEN_TOOLTIP_TAG)?.takeIf { it.isNotEmpty() } ?: PREFS_TOP + this.screenTooltipTag = resolveScreenTooltipTag(arguments?.getString(EXTRA_SCREEN_TOOLTIP_TAG)) preferenceScreen.removeAll() addChildren(children, preferenceScreen) @@ -102,6 +102,9 @@ class IDEPreferencesFragment : BasePreferenceFragment() { } } + /** [EXTRA_SCREEN_TOOLTIP_TAG]'s value, or [PREFS_TOP] if it's missing or blank. */ + internal fun resolveScreenTooltipTag(rawTag: String?): String = rawTag?.takeIf { it.isNotEmpty() } ?: PREFS_TOP + /** The row's own tooltipTag, or null if there's no row at that position or it has none. */ internal fun resolveTooltipTag( recyclerView: RecyclerView, diff --git a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt index 0d2db90372..469711636a 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRunPrefExts.kt @@ -40,7 +40,6 @@ import com.itsaky.androidide.preferences.internal.BuildPreferences.isWarningMode import com.itsaky.androidide.preferences.internal.BuildPreferences.launchAppAfterInstall import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string -import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize @@ -76,20 +75,18 @@ private class GradleCommands( override val title: Int = string.idepref_build_customgradlecommands_title, override val summary: Int? = string.idepref_build_customgradlecommands_summary, override val icon: Int? = drawable.ic_bash_commands, + override val tooltipTag: String = PREFS_BUILDRUN_FLAGS, ) : PropertyBasedMultiChoicePreference() { - @IgnoredOnParcel - override val tooltipTag: String = PREFS_BUILDRUN_FLAGS - - override fun getProperties(): List { + override fun getProperties(): List { return listOf( - PropertyEntry("--stacktrace", ::isStacktraceEnabled, PREFS_BUILDRUN_FLAGS_STACKTRACE), - PropertyEntry("--info", ::isInfoEnabled, PREFS_BUILDRUN_FLAGS_INFO), - PropertyEntry("--debug", ::isDebugEnabled, PREFS_BUILDRUN_FLAGS_DEBUG), - PropertyEntry("--scan", ::isScanEnabled, PREFS_BUILDRUN_FLAGS_SCAN), - PropertyEntry("--warning-mode all", ::isWarningModeAllEnabled, PREFS_BUILDRUN_FLAGS_WARNINGMODEALL), - PropertyEntry("--build-cache", ::isBuildCacheEnabled, PREFS_BUILDRUN_FLAGS_BUILDCACHE), - PropertyEntry("--offline", ::isOfflineEnabled, PREFS_BUILDRUN_FLAGS_OFFLINE), + propertyEntry("--stacktrace", ::isStacktraceEnabled, PREFS_BUILDRUN_FLAGS_STACKTRACE), + propertyEntry("--info", ::isInfoEnabled, PREFS_BUILDRUN_FLAGS_INFO), + propertyEntry("--debug", ::isDebugEnabled, PREFS_BUILDRUN_FLAGS_DEBUG), + propertyEntry("--scan", ::isScanEnabled, PREFS_BUILDRUN_FLAGS_SCAN), + propertyEntry("--warning-mode all", ::isWarningModeAllEnabled, PREFS_BUILDRUN_FLAGS_WARNINGMODEALL), + propertyEntry("--build-cache", ::isBuildCacheEnabled, PREFS_BUILDRUN_FLAGS_BUILDCACHE), + propertyEntry("--offline", ::isOfflineEnabled, PREFS_BUILDRUN_FLAGS_OFFLINE), ) } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt index bd4dbd11f1..929bc95390 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/commonPrefExts.kt @@ -23,23 +23,18 @@ import kotlin.reflect.KMutableProperty0 internal abstract class PropertyBasedMultiChoicePreference : MultiChoicePreference() { -/** One checkbox: its label, the property it toggles, and its own tooltip tag (if any). */ -data class PropertyEntry( - val label: String, - val property: KMutableProperty0, - val tooltipTag: String = "", -) +/** The checkboxes for this screen, in display order. Build each entry via [propertyEntry]. */ +abstract fun getProperties(): List -/** The checkboxes for this screen, in display order; each entry's [PropertyEntry.tooltipTag] is that choice's own long-press help. */ -abstract fun getProperties(): List +/** One checkbox entry backed by a mutable boolean property, read at its current value. */ +protected fun propertyEntry( + label: String, + property: KMutableProperty0, + tooltipTag: String = "", +): PreferenceChoices.Entry = PreferenceChoices.Entry(label, property.get(), property, tooltipTag) -override fun getEntries(preference: Preference): Array { - val properties = getProperties() - return Array(properties.size) { i -> - val entry = properties[i] - PreferenceChoices.Entry(entry.label, entry.property.get(), entry.property, entry.tooltipTag) - } -} +override fun getEntries(preference: Preference): Array = + getProperties().toTypedArray() override fun onChoicesConfirmed( preference: Preference, diff --git a/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt index bae79edf12..0db9ebacd8 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/developerOptionsPrefExts.kt @@ -39,18 +39,6 @@ init { } } -@Parcelize -internal class DebuggingPreferences( -override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING, -override val title: Int = R.string.idepref_group_debugging, -override val children: List = mutableListOf()) : IPreferenceGroup() { - -init { - addPreference(DumpLogsPreference()) - addPreference(EnableLogSenderPreference()) -} -} - @Parcelize internal class DumpLogsPreference( override val key: String = DevOpsPreferences.KEY_DEVOPTS_DEBUGGING_DUMPLOGS, diff --git a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt index 1d6c3d157c..5a4f350919 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/editorPrefExts.kt @@ -164,13 +164,13 @@ override val icon: Int? = drawable.ic_drawing, override val tooltipTag: String = TooltipTag.PREFS_EDITOR_NONPRINTING, ) : PropertyBasedMultiChoicePreference() { -override fun getProperties(): List { +override fun getProperties(): List { return listOf( - PropertyEntry("Leading", EditorPreferences::drawLeadingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_LEADING), - PropertyEntry("Trailing", EditorPreferences::drawTrailingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_TRAILING), - PropertyEntry("Inner", EditorPreferences::drawInnerWs, TooltipTag.PREFS_EDITOR_NONPRINTING_INNER), - PropertyEntry("Empty lines", EditorPreferences::drawEmptyLineWs, TooltipTag.PREFS_EDITOR_NONPRINTING_EMPTYLINES), - PropertyEntry("Line breaks", EditorPreferences::drawLineBreak, TooltipTag.PREFS_EDITOR_NONPRINTING_LINEBREAKS), + propertyEntry("Leading", EditorPreferences::drawLeadingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_LEADING), + propertyEntry("Trailing", EditorPreferences::drawTrailingWs, TooltipTag.PREFS_EDITOR_NONPRINTING_TRAILING), + propertyEntry("Inner", EditorPreferences::drawInnerWs, TooltipTag.PREFS_EDITOR_NONPRINTING_INNER), + propertyEntry("Empty lines", EditorPreferences::drawEmptyLineWs, TooltipTag.PREFS_EDITOR_NONPRINTING_EMPTYLINES), + propertyEntry("Line breaks", EditorPreferences::drawLineBreak, TooltipTag.PREFS_EDITOR_NONPRINTING_LINEBREAKS), ) } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt index 83a30fadb4..37190bf910 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/generalPrefExts.kt @@ -31,7 +31,6 @@ import com.itsaky.androidide.preferences.internal.GeneralPreferences import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string import com.itsaky.androidide.resources.localization.LocaleProvider -import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize @@ -80,12 +79,10 @@ class UiMode( override val key: String = GeneralPreferences.UI_MODE, override val title: Int = R.string.idepref_general_uiMode, override val summary: Int? = R.string.idepref_general_uiMode_summary, -override val icon: Int? = R.drawable.ic_ui_mode +override val icon: Int? = R.drawable.ic_ui_mode, +override val tooltipTag: String = PREFS_GENERAL_UIMODE, ) : SingleChoicePreference() { -@IgnoredOnParcel -override val tooltipTag: String = PREFS_GENERAL_UIMODE - override fun getEntries(preference: Preference): Array { val context = preference.context val currentUiMode = GeneralPreferences.uiMode @@ -116,12 +113,10 @@ class LocaleSelector( override val key: String = GeneralPreferences.SELECTED_LOCALE, override val title: Int = R.string.idepref_general_localeSelector_title, override val summary: Int? = R.string.idepref_general_localeSelector_summary, -override val icon: Int? = R.drawable.ic_translate +override val icon: Int? = R.drawable.ic_translate, +override val tooltipTag: String = PREFS_GENERAL_LANGUAGE, ) : SingleChoicePreference() { -@IgnoredOnParcel -override val tooltipTag: String = PREFS_GENERAL_LANGUAGE - override fun getEntries(preference: Preference): Array { val context = preference.context val currentLocale = GeneralPreferences.selectedLocale diff --git a/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt b/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt index c800eb70fc..4927533e4b 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/pluginPrefExts.kt @@ -11,6 +11,7 @@ import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_PLUGIN_MANAGER import com.itsaky.androidide.plugins.manager.core.PluginManager import com.itsaky.androidide.resources.R.drawable import com.itsaky.androidide.resources.R.string +import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.flashError import kotlinx.parcelize.Parcelize @@ -103,12 +104,18 @@ data class PluginSettingsEntryPreference( * also while the asynchronous plugin load is still in flight - `PreferencesActivity` re-checks on * resume and rebuilds the tree if the set changed. * + * A duplicate [PluginSettingsEntryPreference.key] - two plugins colliding, or one plugin returning + * two entries with the same id - is dropped (keeping the first) rather than reaching the + * Preferences screen's tree: that tree asserts every key is unique and crashes if it isn't, which + * is the right behaviour for a first-party bug but not for third-party plugin data. + * * [pluginManager] defaults to the running IDE's instance; tests pass their own. */ internal fun pluginSettingsPreferences( pluginManager: PluginManager? = IDEApplication.getPluginManager(), -): List = - pluginManager +): List { + val seenKeys = mutableSetOf() + return pluginManager ?.getPluginSettingsEntries() ?.map { (pluginId, entry) -> PluginSettingsEntryPreference( @@ -118,4 +125,13 @@ internal fun pluginSettingsPreferences( summaryText = entry.summary, fragmentClassName = entry.fragmentClassName, ) - }.orEmpty() + } + ?.filter { entry -> + seenKeys.add(entry.key).also { isNew -> + if (!isNew) { + ILogger.ROOT.warn("Dropping plugin settings entry with a duplicate key: {}", entry.key) + } + } + } + .orEmpty() +} diff --git a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt index 8aba76f2c8..1b37cc0985 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/termuxPrefsExt.kt @@ -32,7 +32,6 @@ import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_NOHARDKEYBOARD import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TERMUX_SOFTKEYBOARD import com.termux.shared.logger.Logger import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences -import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import kotlin.reflect.KMutableProperty0 @@ -111,12 +110,10 @@ init { class TermuxDebuggingLogLevelPreference( override val key: String = KEY_TERMUX_DEBUGGING_LOG_LEVEL_PREFERENCE, override val title: Int = R.string.log_level_title, -override val icon: Int? = R.drawable.ic_bug +override val icon: Int? = R.drawable.ic_bug, +override val tooltipTag: String = PREFS_TERMUX_LOGLEVEL, ) : SingleChoicePreference() { -@IgnoredOnParcel -override val tooltipTag: String = PREFS_TERMUX_LOGLEVEL - override fun getEntries(preference: Preference): Array { val logLevels = Logger.getLogLevelsArray() val logLevelLabels = Logger.getLogLevelLabelsArray(preference.context, logLevels, true) diff --git a/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt index e4d2afe922..d9d77a6703 100644 --- a/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt +++ b/app/src/test/java/com/itsaky/androidide/fragments/IDEPreferencesFragmentTest.kt @@ -9,6 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat +import com.itsaky.androidide.idetooltips.TooltipTag.PREFS_TOP import com.itsaky.androidide.preferences.IPreference import com.itsaky.androidide.preferences.IPreferenceGroup import com.itsaky.androidide.preferences.IPreferenceScreen @@ -112,6 +113,21 @@ class IDEPreferencesFragmentTest { assertThat(fragment.resolveTooltipTag(recyclerView, rowA)).isNull() } + @Test + fun `resolveScreenTooltipTag falls back to PREFS_TOP when the argument is missing`() { + assertThat(IDEPreferencesFragment().resolveScreenTooltipTag(null)).isEqualTo(PREFS_TOP) + } + + @Test + fun `resolveScreenTooltipTag falls back to PREFS_TOP when the argument is blank`() { + assertThat(IDEPreferencesFragment().resolveScreenTooltipTag("")).isEqualTo(PREFS_TOP) + } + + @Test + fun `resolveScreenTooltipTag passes through a real tag unchanged`() { + assertThat(IDEPreferencesFragment().resolveScreenTooltipTag("tag.screen")).isEqualTo("tag.screen") + } + /** A real, laid-out RecyclerView backed by a real PreferenceGroupAdapter - not a fake. */ private fun buildRecyclerView(keys: List): RecyclerView { val context = ApplicationProvider.getApplicationContext() diff --git a/docs/docdb/ADFA-5088-preference-tooltips.sql b/docs/docdb/ADFA-5088-preference-tooltips.sql index 721d7ad4a5..1941af19da 100644 --- a/docs/docdb/ADFA-5088-preference-tooltips.sql +++ b/docs/docdb/ADFA-5088-preference-tooltips.sql @@ -87,6 +87,15 @@ CREATE TEMP TABLE _content_guard (content BLOB NOT NULL CHECK (length(content) > CREATE TEMP TABLE _workdir_guard (mode TEXT NOT NULL CHECK (mode = '700')); INSERT INTO _workdir_guard SELECT CAST(READFILE('/tmp/adfa5088-prefs-workdir/.mode') AS TEXT); +-- Remove the dead "prefs.gradle" and "prefs.developer" tags: no code path can +-- reach either any more now that every widget has its own tag. Delete each +-- TooltipButtons row first (it references Tooltips.id via a foreign key). + +DELETE FROM TooltipButtons +WHERE tooltipId IN (SELECT id FROM Tooltips WHERE tag IN ('prefs.gradle', 'prefs.developer') AND categoryId = 1); + +DELETE FROM Tooltips WHERE tag IN ('prefs.gradle', 'prefs.developer') AND categoryId = 1; + -- Tooltips: idempotent upserts (existing empty stub rows + brand new tags, -- all in one form so the script can be re-run safely) @@ -786,6 +795,18 @@ INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.about' AND categoryId = 1) AND uri = 'i/prefs/about'; INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.about' AND categoryId = 1), 1, 'Learn more', 'i/prefs/about'); +-- The 5 tags below aren't inserted by this script (see "Deliberately NOT +-- included" above) - they're assumed to already exist in the real database. +-- Unlike every other tag in this file, that assumption is never verified: a +-- missing tag makes the tooltipId subquery return NULL, and TooltipButtons +-- has no NOT NULL/enforced FK on that column, so a wrong assumption here +-- would silently insert a dead row instead of failing loudly. Assert it. +CREATE TEMP TABLE _existing_tags_guard (found_count INTEGER NOT NULL CHECK (found_count = 5)); +INSERT INTO _existing_tags_guard +SELECT COUNT(*) FROM Tooltips +WHERE categoryId = 1 +AND tag IN ('prefs.general', 'prefs.editor', 'prefs.editor.xml', 'prefs.termux', 'prefs.git'); + DELETE FROM TooltipButtons WHERE tooltipId = (SELECT id FROM Tooltips WHERE tag = 'prefs.general' AND categoryId = 1) AND uri = 'i/prefs/general'; INSERT INTO TooltipButtons (tooltipId, buttonNumberId, description, uri) VALUES ((SELECT id FROM Tooltips WHERE tag = 'prefs.general' AND categoryId = 1), 1, 'Learn more', 'i/prefs/general'); diff --git a/resources/src/main/res/values-ar-rSA/strings.xml b/resources/src/main/res/values-ar-rSA/strings.xml index 8ea2474c6e..fee979321d 100644 --- a/resources/src/main/res/values-ar-rSA/strings.xml +++ b/resources/src/main/res/values-ar-rSA/strings.xml @@ -388,7 +388,6 @@ الاشتراك خيارات المطور خيارات تجريبية/تصحيح أخطاء Code on the Go - تصحيح الأخطاء تفريغ السجلات تفريغ سجلات Code on the Go إلى $HOME/.cg/logs قم بتشغيل النسخة التصحيحية (debug) من تطبيقك لعرض السجلات هنا. diff --git a/resources/src/main/res/values-bn-rIN/strings.xml b/resources/src/main/res/values-bn-rIN/strings.xml index 9824e54355..8a00c256e3 100644 --- a/resources/src/main/res/values-bn-rIN/strings.xml +++ b/resources/src/main/res/values-bn-rIN/strings.xml @@ -388,7 +388,6 @@ বাছাই করুন বিকাশকারী অপশনগুলি Code on the Go-এর জন্য পরীক্ষামূলক/ডিবাগিং অপশনগুলি - ডিবাগিং ডাম্প লগ Code on the Go লগ ডাম্প করুন $HOME/.cg/logs এ এখানে লগগুলি দেখার জন্য আপনার অ্যাপ্লিকেশনটির ডিবাগ ভ্যারিয়েন্ট টি রান করুন৷ diff --git a/resources/src/main/res/values-de-rDE/strings.xml b/resources/src/main/res/values-de-rDE/strings.xml index 7c519e07f2..9c574a308a 100644 --- a/resources/src/main/res/values-de-rDE/strings.xml +++ b/resources/src/main/res/values-de-rDE/strings.xml @@ -387,7 +387,6 @@ Einwilligen Entwickleroptionen Experimentelle/Debugging Optionen für Code on the Go - Debugging Protokolle ausgeben Code on the Go-Logs in $HOME/.cg/logs ausgeben Run the debug variant of your application to view its logs here. diff --git a/resources/src/main/res/values-es-rES/strings.xml b/resources/src/main/res/values-es-rES/strings.xml index 5a279dd0d1..42c64d6dba 100644 --- a/resources/src/main/res/values-es-rES/strings.xml +++ b/resources/src/main/res/values-es-rES/strings.xml @@ -388,7 +388,6 @@ Opt-in Developer options Experimental/debugging options for Code on the Go - Debugging Dump logs Dump Code on the Go logs to $HOME/.cg/logs Run the debug variant of your application to view its logs here. diff --git a/resources/src/main/res/values-fr-rFR/strings.xml b/resources/src/main/res/values-fr-rFR/strings.xml index 59d5c68c5a..b846e0394e 100644 --- a/resources/src/main/res/values-fr-rFR/strings.xml +++ b/resources/src/main/res/values-fr-rFR/strings.xml @@ -389,7 +389,6 @@ Dernier projet ouvert : \n%s S\'inscrire Options développeur Options Expérimental/Débogage d\'Code on the Go - Débogage Vider les logs Dump Code on the Go logs to $HOME/.cg/logs Run the debug variant of your application to view its logs here. diff --git a/resources/src/main/res/values-hi-rIN/strings.xml b/resources/src/main/res/values-hi-rIN/strings.xml index 4dea3e7bf7..1b60ddcfbe 100644 --- a/resources/src/main/res/values-hi-rIN/strings.xml +++ b/resources/src/main/res/values-hi-rIN/strings.xml @@ -387,7 +387,6 @@ चयन करें डेवलपर विकल्प Code on the Go के लिए प्रायोगिक/डिबगिंग विकल्प - डिबगिंग लॉग डंप करें Code on the Go लॉग को $HOME/.cg/logs डायरेक्टरी में डंप करें अपने एप्लिकेशन के लॉग यहां देखने के लिए उसका डिबग संस्करण चलाएँ। diff --git a/resources/src/main/res/values-in-rID/strings.xml b/resources/src/main/res/values-in-rID/strings.xml index fcc01cf432..d07e60b74d 100644 --- a/resources/src/main/res/values-in-rID/strings.xml +++ b/resources/src/main/res/values-in-rID/strings.xml @@ -611,7 +611,6 @@ Setuju Opsi Pengembang Opsi percobaan/debugging untuk Code on the Go - Debugging Simpan log Simpan log Code on the Go ke $HOME/.androidide/logs Jalankan varian debug aplikasi Anda untuk melihat log nya di sini. diff --git a/resources/src/main/res/values-pt-rBR/strings.xml b/resources/src/main/res/values-pt-rBR/strings.xml index 05d34a5447..028769e1b8 100644 --- a/resources/src/main/res/values-pt-rBR/strings.xml +++ b/resources/src/main/res/values-pt-rBR/strings.xml @@ -388,7 +388,6 @@ Aceitar Opções do desenvolvedor Opções experimentais/de depuração para Code on the Go - Depuração Despejar registros Despejar registros do Code on the Go para $HOME/.cg/logs Execute a variante de depuração do seu aplicativo para ver os logs aqui. diff --git a/resources/src/main/res/values-ro-rRO/strings.xml b/resources/src/main/res/values-ro-rRO/strings.xml index 2e68bb0eac..fc8adfa5c5 100644 --- a/resources/src/main/res/values-ro-rRO/strings.xml +++ b/resources/src/main/res/values-ro-rRO/strings.xml @@ -388,7 +388,6 @@ Opt-in Developer options Experimental/debugging options for Code on the Go - Debugging Dump logs Dump Code on the Go logs to $HOME/.cg/logs Run the debug variant of your application to view its logs here. diff --git a/resources/src/main/res/values-ru-rRU/strings.xml b/resources/src/main/res/values-ru-rRU/strings.xml index 78f265d4c5..2313054e55 100644 --- a/resources/src/main/res/values-ru-rRU/strings.xml +++ b/resources/src/main/res/values-ru-rRU/strings.xml @@ -390,7 +390,6 @@ Присоединиться Настройки разработчика Экспериментальные/отладочные опции для Code on the Go - Отладка Дамп логов Дамп логов Code on the Go в $HOME/.cg/logs Запустите отладочный вариант вашего приложения, чтобы увидеть его логи здесь. diff --git a/resources/src/main/res/values-tr-rTR/strings.xml b/resources/src/main/res/values-tr-rTR/strings.xml index 2cd1cac2db..95ff89cab3 100644 --- a/resources/src/main/res/values-tr-rTR/strings.xml +++ b/resources/src/main/res/values-tr-rTR/strings.xml @@ -388,7 +388,6 @@ Katıl Geliştirici seçenekleri Code on the Go için deneysel/hata ayıklama seçenekleri - Hata ayıklama Günlükleri kaydet $HOME/.cg/logs içine Code on the Go günlüklerini kaydet Uygulamanızın günlüklerini burada görüntülemek için debug varyantını çalıştırın. diff --git a/resources/src/main/res/values-zh-rCN/strings.xml b/resources/src/main/res/values-zh-rCN/strings.xml index 494c564c4e..74d7bd317d 100644 --- a/resources/src/main/res/values-zh-rCN/strings.xml +++ b/resources/src/main/res/values-zh-rCN/strings.xml @@ -671,7 +671,6 @@ 同意 开发者选项 Code on the Go 的实验/调试选项 - 调试 转储日志 将 Code on the Go 日志转储到 $HOME/.cg/logs 运行应用的调试版本以在此查看其日志 diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index b8e7660fef..55ea5eede5 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -676,7 +676,6 @@ Opt-in Developer Options Experimental/debugging options for Code on the Go - Debugging Dump logs Dump Code on the Go logs to $HOME/.cg/logs Run the debug variant of your application to view its logs here.