6.7.0 - Alpha19 - 修复代码编辑器在只读模式下已崩溃且依然可以编辑代码内容的问题
This commit is contained in:
@@ -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)_",
|
||||
"崩溃报告页面复制详细信息功能失效的问题",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user