6.7.0 - Alpha19 - 修复代码编辑器在只读模式下已崩溃且依然可以编辑代码内容的问题

This commit is contained in:
SuperMonster003
2026-02-08 00:41:07 +08:00
parent 4dc0f7c60b
commit 7307c29711
7 changed files with 363 additions and 107 deletions

View File

@@ -1,7 +1,7 @@
{
"$data": {
"v6.7.0": {
"released_date": "2026/02/04",
"released_date": "2026/02/08",
"feature": [
"插件中心功能, 支持插件的安装/卸载/更新等操作 (入口: 主页抽屉按钮/主页标签页)",
"版本历史功能, 支持查看/恢复可编辑文件的历史版本 (入口: 主页抽屉按钮/文件管理器菜单/代码编辑器菜单)",
@@ -96,6 +96,9 @@
"发行历史页面部分系统因字体差别导致统计数据显示不完整的问题",
"部分设备无法正常初始化 MLKit Google OCR 的问题 (试修) _[`issue #8`](http://issues.autojs6.com/8#issuecomment-3117061768)_",
"部分设备无法正常触发文件管理器功能按钮点击事件的问题 (试修) _[`issue #465`](http://issues.autojs6.com/465)_",
"部分设备代码编辑器空行显示方框字符的问题 (试修)",
"代码编辑器在只读模式下依然可以编辑代码内容的问题",
"代码编辑器在只读模式下点击标题区域及部分菜单项导致应用崩溃的问题",
"ErrorDialogActivity 可能无法正常启动或短时间自动消失的问题 _[`issue #479`](http://issues.autojs6.com/479)_ _[`issue #471`](http://issues.autojs6.com/471)_ _[`issue #414`](http://issues.autojs6.com/414)_ _[`issue #340`](http://issues.autojs6.com/340#issuecomment-2973485826)_",
"Canvas 构造函数可接受的参数类型错误 _[`issue #402`](http://issues.autojs6.com/402)_",
"崩溃报告页面复制详细信息功能失效的问题",

View File

@@ -28,8 +28,6 @@ import org.autojs.autojs.app.OnActivityResultDelegate.DelegateHost
import org.autojs.autojs.core.permission.OnRequestPermissionsResultCallback
import org.autojs.autojs.core.permission.PermissionRequestProxyActivity
import org.autojs.autojs.core.permission.RequestPermissionCallbacks
import org.autojs.autojs.util.ViewUtils.setOnTitleViewClickListener
import org.autojs.autojs.util.ViewUtils.titleView
import org.autojs.autojs.pio.PFiles
import org.autojs.autojs.storage.file.TmpScriptFiles
import org.autojs.autojs.theme.widget.ThemeColorToolbar
@@ -40,6 +38,8 @@ import org.autojs.autojs.util.IntentUtils.startSafely
import org.autojs.autojs.util.Observers
import org.autojs.autojs.util.ViewUtils.onceGlobalLayout
import org.autojs.autojs.util.ViewUtils.setMenuIconsColorByThemeColorLuminance
import org.autojs.autojs.util.ViewUtils.setOnTitleViewClickListener
import org.autojs.autojs.util.ViewUtils.titleView
import org.autojs.autojs6.R
import org.autojs.autojs6.databinding.ActivityEditBinding
import java.io.File
@@ -51,6 +51,8 @@ import java.io.IOException
*/
open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyActivity {
private var mReadOnly: Boolean = false
private val mOnBackPressedCallback = object : OnBackPressedCallback(true) {
// override fun onBackPressed() {
@@ -85,12 +87,17 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
super.onCreate(savedInstanceState)
mReadOnly = intent.getBooleanExtra(EditorView.EXTRA_READ_ONLY, false)
val binding = ActivityEditBinding.inflate(layoutInflater).also { setContentView(it.root) }
mToolbar = findViewById<ThemeColorToolbar>(R.id.toolbar).apply {
setTitleTextAppearance(this@EditActivity, R.style.TextAppearanceEditorTitle)
setOnTitleViewClickListener {
EditableFileInfoDialogManager.showEditableFileInfoDialog(this@EditActivity, mEditorView.uri.path?.let { File(it) }) {
mEditorView.editor.text
val path = mEditorView.uri?.path
if (path != null) {
EditableFileInfoDialogManager.showEditableFileInfoDialog(this@EditActivity, File(path)) {
mEditorView.editor.text
}
}
}
}
@@ -99,7 +106,7 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
.observeOn(AndroidSchedulers.mainThread())
.subscribe(Observers.emptyConsumer()) { ex: Throwable -> onLoadFileError(ex.message) }
}
mEditorMenu = EditorMenu(mEditorView)
mEditorMenu = EditorMenu(mEditorView, mReadOnly)
mNewTask = intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0
setUpToolbar()
@@ -130,6 +137,11 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
return true
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
mEditorMenu.prepareOptionsMenu(menu)
return super.onPrepareOptionsMenu(menu)
}
private fun TextView.adjustTitleTextView() = this.post {
ValueAnimator.ofFloat(this.textSize, calculatedTextSize(this) ?: return@post).let { animator ->
animator.duration = 120L
@@ -247,7 +259,9 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
val menu = mode.menu
val item = menu[menu.size - 1]
addMenuItem(menu, item.groupId, R.id.action_delete_line, 10000, R.string.text_delete_line) { mEditorMenu.deleteLine() }
if (!mReadOnly) {
addMenuItem(menu, item.groupId, R.id.action_delete_line, 10000, R.string.text_delete_line) { mEditorMenu.deleteLine() }
}
addMenuItem(menu, item.groupId, R.id.action_copy_line, 20000, R.string.text_copy_line) { mEditorMenu.copyLine() }
super.onActionModeStarted(mode)

View File

@@ -2,15 +2,17 @@ package org.autojs.autojs.ui.edit;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.text.InputType;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.Nullable;
import com.afollestad.materialdialogs.MaterialDialog;
import io.reactivex.android.schedulers.AndroidSchedulers;
import org.autojs.autojs.util.DialogUtils;
import org.autojs.autojs.core.pref.Language;
import org.autojs.autojs.core.pref.Pref;
import org.autojs.autojs.util.MaterialDialogUtils;
import org.autojs.autojs.model.indices.AndroidClass;
import org.autojs.autojs.model.indices.ClassSearchingItem;
import org.autojs.autojs.script.JavaScriptFileSource;
@@ -35,18 +37,56 @@ import static org.autojs.autojs.util.StringUtils.key;
/**
* Created by Stardust on Sep 28, 2017.
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressWarnings({"ResultOfMethodCallIgnored", "unused"})
@SuppressLint("CheckResult")
public class EditorMenu {
private final EditorView mEditorView;
private final Context mContext;
private final CodeEditor mEditor;
private final boolean mReadOnly;
public EditorMenu(EditorView editorView) {
this(editorView, false);
}
public EditorMenu(EditorView editorView, boolean readOnly) {
mEditorView = editorView;
mContext = editorView.getContext();
mEditor = editorView.editor;
mReadOnly = readOnly;
}
public void prepareOptionsMenu(Menu menu) {
if (menu == null) {
return;
}
if (!mReadOnly) {
return;
}
setMenuItemInvisible(menu, R.id.action_find_or_replace);
setMenuItemInvisible(menu, R.id.action_copy_line);
setMenuItemInvisible(menu, R.id.action_paste);
setMenuItemInvisible(menu, R.id.action_delete_line);
setMenuItemInvisible(menu, R.id.action_clear);
setMenuItemInvisible(menu, R.id.action_comment);
setMenuItemInvisible(menu, R.id.action_beautify);
setMenuItemInvisible(menu, R.id.action_jump);
setMenuItemInvisible(menu, R.id.action_debug);
setMenuItemInvisible(menu, R.id.action_build_apk);
setMenuItemInvisible(menu, R.id.action_console);
setMenuItemInvisible(menu, R.id.action_import_java_class);
setMenuItemInvisible(menu, R.id.action_file_details);
setMenuItemInvisible(menu, R.id.action_version_history);
setMenuItemInvisible(menu, R.id.action_fx_keyboard);
setMenuItemInvisible(menu, R.id.action_open_by_other_apps);
}
private void setMenuItemInvisible(Menu menu, int itemId) {
MenuItem item = menu.findItem(itemId);
if (item != null) {
item.setVisible(false);
}
}
public boolean onOptionsItemSelected(MenuItem item) {
@@ -75,7 +115,7 @@ public class EditorMenu {
.content(R.string.hint_long_click_run_to_debug)
.positiveText(R.string.dialog_button_dismiss)
.positiveColorRes(R.color.dialog_button_default);
MaterialDialogUtils.widgetThemeColor(builder);
DialogUtils.widgetThemeColor(builder);
builder.show();
return tryDoing(mEditorView::debug);
}
@@ -216,7 +256,22 @@ public class EditorMenu {
}
private void startBuildApkActivity() {
BuildActivity.launch(mContext, mEditorView.uri.getPath());
Uri uri = mEditorView.getUri();
String path;
if (uri == null) {
path = null;
} else {
path = uri.getPath();
}
if (TextUtils.isEmpty(path)) {
DialogUtils.buildAndShowAdaptive(new MaterialDialog.Builder(mContext)
.title(R.string.text_prompt)
.content(R.string.error_unable_to_package_application_as_current_file_path_is_unknown)
.positiveText(R.string.dialog_button_dismiss)
.positiveColorRes(R.color.dialog_button_default)::build);
return;
}
BuildActivity.launch(mContext, path);
}
private void setPinchToZoomStrategy() {
@@ -256,7 +311,7 @@ public class EditorMenu {
.onPositive((dialog, which) -> dialog.dismiss())
.autoDismiss(false);
MaterialDialogUtils.choiceWidgetThemeColor(builder);
DialogUtils.choiceWidgetThemeColor(builder);
// TODO by SuperMonster003 on Oct 17, 2022.
// ! Implementation for "scale view".
@@ -338,12 +393,26 @@ public class EditorMenu {
builder.positiveColorRes(R.color.dialog_button_attraction);
builder.negativeText(R.string.dialog_button_cancel);
builder.negativeColorRes(R.color.dialog_button_default);
MaterialDialogUtils.widgetThemeColor(builder);
DialogUtils.widgetThemeColor(builder);
builder.show();
}
private void showFileDetails() {
var path = mEditorView.uri.getPath();
Uri uri = mEditorView.getUri();
String path;
if (uri != null) {
path = uri.getPath();
} else {
path = null;
}
if (TextUtils.isEmpty(path)) {
DialogUtils.buildAndShowAdaptive(new MaterialDialog.Builder(mContext)
.title(R.string.text_prompt)
.content(R.string.error_unable_to_display_file_details_as_current_file_path_is_unknown)
.positiveText(R.string.dialog_button_dismiss)
.positiveColorRes(R.color.dialog_button_default)::build);
return;
}
EditableFileInfoDialogManager.showEditableFileInfoDialog(mContext, new File(path), mEditor::getText);
}

View File

@@ -26,7 +26,7 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.autojs.autojs.AutoJs
import org.autojs.autojs.app.DialogUtils
import org.autojs.autojs.util.DialogUtils
import org.autojs.autojs.core.pref.Pref.getEditorTextSize
import org.autojs.autojs.core.pref.Pref.setEditorTextSize
import org.autojs.autojs.engine.JavaScriptEngine
@@ -47,6 +47,7 @@ import org.autojs.autojs.model.script.Scripts.runWithBroadcastSender
import org.autojs.autojs.pio.PFiles.getNameWithoutExtension
import org.autojs.autojs.pio.PFiles.write
import org.autojs.autojs.storage.file.TmpScriptFiles
import org.autojs.autojs.storage.history.HistoryPrefs
import org.autojs.autojs.storage.history.HistoryRepository
import org.autojs.autojs.storage.history.HistoryUriUtils
import org.autojs.autojs.storage.history.VersionHistoryController
@@ -73,12 +74,14 @@ import org.autojs.autojs.ui.widget.SimpleTextWatcher
import org.autojs.autojs.util.ClipboardUtils
import org.autojs.autojs.util.DisplayUtils.pxToSp
import org.autojs.autojs.util.DocsUtils.getUrl
import org.autojs.autojs.util.MaterialDialogUtils.choiceWidgetThemeColor
import org.autojs.autojs.util.DialogUtils.choiceWidgetThemeColor
import org.autojs.autojs.util.Observers
import org.autojs.autojs.util.StringUtils
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs.util.ViewUtils.showSnack
import org.autojs.autojs.util.ViewUtils.showToast
import org.autojs.autojs6.R
import org.autojs.autojs6.R.string.text_unknown
import org.autojs.autojs6.databinding.EditorViewBinding
import java.io.File
import java.io.IOException
@@ -104,9 +107,15 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
@JvmField
val debugBar: DebugBar = binding.debugBar
lateinit var name: String
private var _name: String? = null
lateinit var uri: Uri
var name: String
get() = _name ?: "[ ${this.context.getString(text_unknown)} ]"
set(value) {
_name = value
}
var uri: Uri? = null
var scriptExecutionId = 0
private set
@@ -204,19 +213,31 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
}
fun handleIntent(intent: Intent): Observable<String> {
intent.getStringExtra(EXTRA_NAME)?.let { name = it }
val name = intent.getStringExtra(EXTRA_NAME)
if (name != null) {
this.name = name
}
val readOnly = intent.getBooleanExtra(EXTRA_READ_ONLY, false).also {
mReadOnly = it
}
if (readOnly) {
mInputMethodEnhanceBar.visibility = GONE
}
return handleText(intent)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
mReadOnly = intent.getBooleanExtra(EXTRA_READ_ONLY, false)
val saveEnabled = intent.getBooleanExtra(EXTRA_SAVE_ENABLED, true)
if (mReadOnly || !saveEnabled) {
if (!saveEnabled) {
findViewById<View>(R.id.save).visibility = GONE
} else if (readOnly) {
findViewById<View>(R.id.undo).visibility = GONE
findViewById<View>(R.id.redo).visibility = GONE
findViewById<View>(R.id.save).visibility = GONE
}
if (!intent.getBooleanExtra(EXTRA_RUN_ENABLED, true)) {
findViewById<View>(R.id.run).visibility = GONE
}
if (mReadOnly) {
if (readOnly) {
editor.setReadOnly(true)
}
}
@@ -228,21 +249,28 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
}
private fun handleText(intent: Intent): Observable<String> {
val path = intent.getStringExtra(EXTRA_PATH)
val content = intent.getStringExtra(EXTRA_CONTENT)
if (content != null) {
setInitialText(content)
return Observable.just(content)
}
uri = if (path == null) {
intent.data ?: return Observable.error(IllegalArgumentException("path and content is empty"))
} else {
Uri.fromFile(File(path))
val path = intent.getStringExtra(EXTRA_PATH)
val uri = path?.let {
Uri.fromFile(File(it))
} ?: intent.data
this.uri = uri
if (_name == null && uri != null) {
uri.path?.let {
name = getNameWithoutExtension(it)
}
}
if (!::name.isInitialized) {
name = getNameWithoutExtension(uri.path!!)
return when {
content != null -> Observable.just(content)
uri != null -> loadUri(uri)
else -> Observable.error(IllegalArgumentException("path and content is empty"))
}
return loadUri(uri)
}
@SuppressLint("CheckResult")
@@ -295,7 +323,12 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
private fun initNormalToolbar() {
mNormalToolbar.apply {
setOnMenuItemClickListener(this@EditorView)
setOnMenuItemLongClickListener { id -> if (id == R.id.run) true.also { debug() } else false }
setOnMenuItemLongClickListener { id ->
when (id) {
R.id.run if !mReadOnly -> true.also { debug() }
else -> false
}
}
}
activity.supportFragmentManager.findFragmentById(R.id.toolbar_menu) ?: showNormalToolbar()
}
@@ -375,6 +408,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
mCodeCompletionBar.setTextColor(textColor)
mSymbolBar.setTextColor(textColor)
mShowFunctionsButton.setColorFilter(textColor)
ViewUtils.setNavigationBarBackgroundColor(activity, it.imeBarBackgroundColor)
invalidate()
}
}
@@ -412,7 +446,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
}
@JvmOverloads
fun run(showMessage: Boolean, file: File? = uri.path?.let { File(it) }, overriddenFullPath: String? = null): ScriptExecution? {
fun run(showMessage: Boolean, file: File? = uri?.path?.let { File(it) }, overriddenFullPath: String? = null): ScriptExecution? {
file ?: return null
if (showMessage) {
showSnack(this, R.string.text_start_running)
@@ -420,7 +454,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
// TODO by Stardust on Oct 24, 2018.
val execution = runWithBroadcastSender(
file,
workingDirectory = uri.path?.let { File(it).parent },
workingDirectory = uri?.path?.let { File(it).parent },
overriddenFullPath,
) ?: return null
scriptExecutionId = execution.id
@@ -428,8 +462,8 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
return execution
}
private fun runTmpFile(file: File? = uri.path?.let { File(it) }): ScriptExecution? {
return run(true, file, uri.path)
private fun runTmpFile(file: File? = uri?.path?.let { File(it) }): ScriptExecution? {
return run(true, file, uri?.path)
}
private fun undo() = editor.undo()
@@ -439,10 +473,17 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
fun save(): Observable<String> =
Observable
.fromCallable {
// Use a transactional save flow for both file:// and content://.
// zh-CN: 对 file:// 与 content:// 统一使用事务式保存流程.
editor.text.apply {
writeTextWithCharsetTransactional(uri, this)
when (val uri = uri) {
null -> {
throw IllegalStateException(context.getString(R.string.error_unable_to_save_file_as_current_file_path_is_unknown))
}
else -> {
// Use a transactional save flow for both file:// and content://.
// zh-CN: 对 file:// 与 content:// 统一使用事务式保存流程.
editor.text.apply {
writeTextWithCharsetTransactional(uri, this)
}
}
}
}
.subscribeOn(Schedulers.io())
@@ -506,7 +547,6 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
if (logicalPath != null && shouldTrackHistory(oldBytes, newBytes) && oldBytes != null) {
runCatching {
HistoryRepository(context.applicationContext).recordSavePre(
uri = uri,
logicalPath = logicalPath,
oldBytes = oldBytes,
encodingName = targetCharset.name(),
@@ -689,7 +729,9 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
private fun shouldTrackHistory(oldBytes: ByteArray?, newBytes: ByteArray): Boolean {
val oldSize = oldBytes?.size ?: 0
val newSize = newBytes.size
return oldSize <= MAX_FILE_SIZE_TO_TRACK_BYTES && newSize <= MAX_FILE_SIZE_TO_TRACK_BYTES
val limit = HistoryPrefs.maxFileSizeToTrackBytes().coerceAtLeast(0L)
return oldSize.toLong() <= limit && newSize.toLong() <= limit
}
private fun sha256(bytes: ByteArray): ByteArray {
@@ -711,18 +753,32 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
}
fun showVersionHistoryDialog() {
// Delegate to controller to unify editor/explorer/history page behavior.
// zh-CN: 委托给 Controller, 统一 editor/explorer/history page 的行为.
VersionHistoryController(context).showForEditor(
uri = uri,
onRestoreToEditor = { restoredText ->
editor.text = restoredText
},
onRestoredUi = {
setMenuItemStatus(R.id.save, true)
showSnack(this@EditorView, R.string.text_done)
},
)
when (val uri = uri) {
null -> {
DialogUtils.buildAndShowAdaptive {
MaterialDialog.Builder(context)
.title(R.string.text_prompt)
.content(R.string.error_unable_to_display_version_history_as_current_file_path_is_unknown)
.positiveText(R.string.dialog_button_dismiss)
.positiveColorRes(R.color.dialog_button_default)
.build()
}
}
else -> {
// Delegate to controller to unify editor/explorer/history page behavior.
// zh-CN: 委托给 Controller, 统一 editor/explorer/history page 的行为.
VersionHistoryController(context).showForEditor(
uri = uri,
onRestoreToEditor = { restoredText ->
editor.text = restoredText
},
onRestoredUi = {
setMenuItemStatus(R.id.save, true)
showSnack(this@EditorView, R.string.text_done)
},
)
}
}
}
// A minimal emergency draft store.
@@ -757,9 +813,10 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
val now = System.currentTimeMillis()
// 1) Remove expired (older than 7 days).
// zh-CN: 1) 删除过期草稿 (超过 7 天).
val expiredBefore = now - DRAFT_MAX_DAYS_MS
// 1) Remove expired.
// zh-CN: 1) 删除过期草稿.
val maxDraftLifetime = HistoryPrefs.draftsMaxDays().coerceAtLeast(0).toLong() * 24L * 60L * 60L * 1000L
val expiredBefore = now - maxDraftLifetime
files.forEach { f ->
if (f.lastModified() < expiredBefore) {
// noinspection ResultOfMethodCallIgnored
@@ -772,10 +829,12 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
val remained = draftsDir.listFiles()?.toList()?.sortedByDescending { it.lastModified() } ?: return
var total = remained.sumOf { it.length().coerceAtLeast(0L) }
if (total <= DRAFT_MAX_TOTAL_BYTES) return
val draftLimit = HistoryPrefs.draftsMaxTotalBytes().coerceAtLeast(0L)
if (total <= draftLimit) return
for (f in remained.asReversed()) {
if (total <= DRAFT_MAX_TOTAL_BYTES) break
if (total <= draftLimit) break
val len = f.length().coerceAtLeast(0L)
if (f.delete()) {
total -= len
@@ -823,7 +882,16 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
}
fun openByOtherApps() {
openByOtherApps(uri)
uri?.let { openByOtherApps(it) } ?: run {
DialogUtils.buildAndShowAdaptive {
MaterialDialog.Builder(context)
.title(R.string.text_prompt)
.content(R.string.error_unable_to_open_with_other_apps_as_current_file_path_is_unknown)
.positiveText(R.string.dialog_button_dismiss)
.positiveColorRes(R.color.dialog_button_default)
.build()
}
}
}
fun beautifyCode() {
@@ -843,10 +911,12 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
}
fun selectTextSize() {
TextSizeSettingDialogBuilder(context)
.initialValue(pxToSp(editor.codeEditText.textSize).toInt())
.callback { value: Int -> setTextSize(value) }
.show()
DialogUtils.buildAndShowAdaptive {
TextSizeSettingDialogBuilder(context)
.initialValue(pxToSp(editor.codeEditText.textSize).toInt())
.callback { value: Int -> setTextSize(value) }
.build()
}
}
fun setTextSize(value: Int) {
@@ -860,22 +930,24 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
if (i < 0) {
i = 0
}
MaterialDialog.Builder(context)
.title(R.string.text_editor_theme)
.items(themes)
.choiceWidgetThemeColor()
.itemsCallbackSingleChoice(i) { _, _, which, _ ->
themes[which]?.let {
setTheme(it)
Themes.setCurrent(it.name)
DialogUtils.buildAndShowAdaptive {
MaterialDialog.Builder(context)
.title(R.string.text_editor_theme)
.items(themes)
.choiceWidgetThemeColor()
.itemsCallbackSingleChoice(i) { _, _, which, _ ->
themes[which]?.let {
setTheme(it)
Themes.setCurrent(it.name)
}
true
}
true
}
.negativeText(R.string.dialog_button_cancel)
.negativeColorRes(R.color.dialog_button_default)
.positiveText(R.string.dialog_button_confirm)
.positiveColorRes(R.color.dialog_button_attraction)
.show()
.negativeText(R.string.dialog_button_cancel)
.negativeColorRes(R.color.dialog_button_default)
.positiveText(R.string.dialog_button_confirm)
.positiveColorRes(R.color.dialog_button_attraction)
.build()
}
}
@Throws(CheckedPatternSyntaxException::class)
@@ -910,7 +982,9 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
.replace(R.id.toolbar_menu, DebugToolbarFragment())
.commit()
debugBar.visibility = VISIBLE
mInputMethodEnhanceBar.visibility = GONE
if (!mReadOnly) {
mInputMethodEnhanceBar.visibility = GONE
}
mDebugging = true
}
@@ -923,7 +997,9 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
showNormalToolbar()
editor.setDebuggingLine(-1)
debugBar.visibility = GONE
mInputMethodEnhanceBar.visibility = VISIBLE
if (!mReadOnly) {
mInputMethodEnhanceBar.visibility = VISIBLE
}
mDebugging = false
}
@@ -1071,18 +1147,6 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
private const val MIN_CONFIDENCE_TO_WRITE_FILE = 90
private val DEFAULT_CHARSET_TO_WRITE_FILE = StandardCharsets.UTF_8
// Max file size to track history snapshots.
// zh-CN: 纳管历史快照的最大文件大小.
private const val MAX_FILE_SIZE_TO_TRACK_BYTES: Int = 20 * 1024 * 1024
// Emergency draft cleanup window (7 days).
// zh-CN: 草稿清理时间窗口 (7 天).
private const val DRAFT_MAX_DAYS_MS: Long = 7L * 24L * 60L * 60L * 1000L
// Emergency draft total size limit (200MB).
// zh-CN: 草稿总容量上限 (200MB).
private const val DRAFT_MAX_TOTAL_BYTES: Long = 200L * 1024L * 1024L
// Internal storage root (no external SD).
// zh-CN: 内部存储根目录 (不访问外置 SD).
private const val INTERNAL_STORAGE_ROOT: String = "/storage/emulated/0"
@@ -1094,4 +1158,4 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
const val EXTRA_SAVE_ENABLED = "saveEnabled"
const val EXTRA_RUN_ENABLED = "runEnabled"
}
}
}

View File

@@ -33,7 +33,11 @@ import android.os.Parcelable
import android.text.Layout
import android.util.AttributeSet
import android.view.Gravity
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputConnectionWrapper
import android.widget.TextViewHelper
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.graphics.withTranslation
@@ -68,6 +72,14 @@ class CodeEditText : AppCompatEditText {
private var mDebuggingLine = -1
private var mCursorChangeCallbacks: CopyOnWriteArrayList<CursorChangeCallback>? = null
// Read-only state.
// zh-CN: 只读状态.
private var mReadOnly = false
// Backup of the original KeyListener, used to restore editable behavior.
// zh-CN: 备份原始 KeyListener, 用于恢复可编辑行为.
private var mOriginalKeyListener = keyListener
private val currentLine: Int
get() = layout?.let { LayoutHelper.getLineOfChar(it, selectionStart) } ?: -1
@@ -99,6 +111,86 @@ class CodeEditText : AppCompatEditText {
importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
}
mCursorChangeCallbacks = CopyOnWriteArrayList()
// Ensure selection is possible by default.
// zh-CN: 默认确保可以进行文本选择.
setTextIsSelectable(true)
isLongClickable = true
}
// Public API to toggle read-only mode.
// zh-CN: 切换只读模式的公开接口.
fun setReadOnly(readOnly: Boolean) {
if (mReadOnly == readOnly) return
mReadOnly = readOnly
if (readOnly) {
// Keep enabled/focusable so the user can select and copy text.
// zh-CN: 保持 enabled/focusable, 让用户可以选择并复制文本.
isEnabled = true
isFocusable = true
isFocusableInTouchMode = true
setTextIsSelectable(true)
isLongClickable = true
isCursorVisible = true
// Disable soft keyboard editing by removing KeyListener.
// zh-CN: 通过移除 KeyListener 禁用软键盘编辑.
if (mOriginalKeyListener == null) {
mOriginalKeyListener = keyListener
}
keyListener = null
showSoftInputOnFocus = false
} else {
// Restore editable behavior.
// zh-CN: 恢复可编辑行为.
keyListener = mOriginalKeyListener
showSoftInputOnFocus = true
}
}
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
val ic = super.onCreateInputConnection(outAttrs) ?: return null
if (!mReadOnly) return ic
// Block all text-mutating operations at the InputConnection layer.
// zh-CN: 在 InputConnection 层阻止所有会修改文本的操作.
return object : InputConnectionWrapper(ic, true) {
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean = false
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean = false
override fun finishComposingText(): Boolean = false
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean = false
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean = false
}
}
override fun onTextContextMenuItem(id: Int): Boolean {
if (mReadOnly) {
// Allow copy/select actions, block cut/paste/replace actions.
// zh-CN: 允许复制/选择相关操作, 阻止剪切/粘贴/替换等修改操作.
when (id) {
android.R.id.cut,
android.R.id.paste,
android.R.id.pasteAsPlainText,
android.R.id.replaceText -> return false
}
}
return super.onTextContextMenuItem(id)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (mReadOnly) {
// Block hardware-keyboard editing shortcuts.
// zh-CN: 阻止硬件键盘的编辑快捷键.
if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
return true
}
if (event.isCtrlPressed && (keyCode == KeyEvent.KEYCODE_V || keyCode == KeyEvent.KEYCODE_X)) {
return true
}
}
return super.onKeyDown(keyCode, event)
}
@SuppressLint("WrongConstant")
@@ -237,23 +329,32 @@ class CodeEditText : AppCompatEditText {
// Draw code
val lineStart = layout.getLineStart(line)
val lineVisibleEnd = layout.getLineVisibleEnd(line)
// Never draw line-break control characters.
// zh-CN: 永远不要绘制换行等控制字符.
val safeText = text ?: continue
var lineEnd = layout.getLineEnd(line).coerceAtMost(safeText.length)
while (lineEnd > lineStart) {
val ch = safeText[lineEnd - 1]
if (ch == '\n' || ch == '\r') {
lineEnd--
} else {
break
}
}
if (lineStart >= textLength) continue
if (lineVisibleEnd > textLength) continue
if (lineEnd > textLength) continue
// @Reference to LYS86 (https://github.com/LYS86) by SuperMonster003 on Apr 17, 2025.
// ! https://github.com/LYS86/AutoJs/blob/05a7e48a8d5b0c6207b3d2974f762c050156298c/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditText.java#L232
if (lineStart == lineVisibleEnd) continue
// If this is an empty line (or the line only contains line-break chars), skip drawing.
// zh-CN: 如果这是空白行(或该行只包含换行字符), 则跳过绘制.
if (lineStart >= lineEnd) continue
val lineEnd = lineVisibleEnd.coerceAtMost(highlightTokens.colors.size)
val localColors = highlightTokens.colors
val visibleCharStart = getVisibleCharIndex(paint, scrollX, lineStart, lineEnd)
var visibleCharEnd = getVisibleCharIndex(paint, scrollX + mParentScrollView!!.width, lineStart, lineEnd) + 1
val safeText = text ?: continue
val localColors = highlightTokens.colors
if (visibleCharStart >= visibleCharEnd) continue
if (visibleCharStart >= safeText.length) continue
if (visibleCharStart < 0) continue
@@ -290,7 +391,7 @@ class CodeEditText : AppCompatEditText {
}
paint.color = previousColor
visibleCharEnd = visibleCharEnd.coerceAtMost(textLength)
visibleCharEnd = minOf(visibleCharEnd.coerceAtMost(textLength), lineEnd)
if (previousColorPos >= visibleCharEnd) continue
val currentText = text ?: continue

View File

@@ -51,8 +51,9 @@ import kotlin.math.floor
* Modified by project: https://github.com/980008027/JsDroidEditor
*/
/**
* Modified by SuperMonster003 as of Jul 16, 2023.
* Transformed by SuperMonster003 on Jul 16, 2023.
* Modified by SuperMonster003 as of Jul 16, 2023.
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 6, 2026.
*/
class CodeEditor : HVScrollView {
@@ -293,7 +294,7 @@ class CodeEditor : HVScrollView {
}
fun setReadOnly(readOnly: Boolean) {
codeEditText.isEnabled = !readOnly
codeEditText.setReadOnly(readOnly)
}
fun setRedoUndoEnabled(enabled: Boolean) {

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.ui.edit.toolbar;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -93,7 +94,10 @@ public class DebugToolbarFragment extends ToolbarFragment<FragmentDebugToolbarBi
mDebugger = DebuggerSingleton.get();
mDebugger.setWeakDebugCallback(new WeakReference<>(this));
setInterrupted(false);
mCurrentEditorSourceUrl = mInitialEditorSourceUrl = mEditorView.uri.toString();
Uri uri = mEditorView.getUri();
if (uri != null) {
mCurrentEditorSourceUrl = mInitialEditorSourceUrl = uri.toString();
}
mInitialEditorSource = mEditorView.editor.getText();
setupEditor();
ScriptExecution execution = mEditorView.run(false);