6.7.0 - Alpha19 - 修复代码编辑器软键盘可能遮挡文本内容的问题

This commit is contained in:
SuperMonster003
2026-02-08 15:17:18 +08:00
parent 29fba208b0
commit dc88dfc45f
4 changed files with 148 additions and 140 deletions

View File

@@ -97,6 +97,7 @@
"部分设备无法正常初始化 MLKit Google OCR 的问题 (试修) _[`issue #8`](http://issues.autojs6.com/8#issuecomment-3117061768)_", "部分设备无法正常初始化 MLKit Google OCR 的问题 (试修) _[`issue #8`](http://issues.autojs6.com/8#issuecomment-3117061768)_",
"部分设备无法正常触发文件管理器功能按钮点击事件的问题 (试修) _[`issue #465`](http://issues.autojs6.com/465)_", "部分设备无法正常触发文件管理器功能按钮点击事件的问题 (试修) _[`issue #465`](http://issues.autojs6.com/465)_",
"部分设备代码编辑器空行显示方框字符的问题 (试修)", "部分设备代码编辑器空行显示方框字符的问题 (试修)",
"代码编辑器软键盘可能遮挡文本内容的问题",
"代码编辑器在只读模式下依然可以编辑代码内容的问题", "代码编辑器在只读模式下依然可以编辑代码内容的问题",
"代码编辑器在只读模式下点击标题区域及部分菜单项导致应用崩溃的问题", "代码编辑器在只读模式下点击标题区域及部分菜单项导致应用崩溃的问题",
"代码编辑器加载大文件可能导致应用崩溃的问题 (试修)", "代码编辑器加载大文件可能导致应用崩溃的问题 (试修)",

View File

@@ -65,6 +65,8 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
if (mEditorView.onBackPressed()) { if (mEditorView.onBackPressed()) {
return return
} }
mEditorView.cancelLargeFileLoading()
isEnabled = false isEnabled = false
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
isEnabled = true isEnabled = true

View File

@@ -8,6 +8,7 @@ import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.database.Cursor import android.database.Cursor
import android.graphics.Rect
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -25,6 +26,9 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
@@ -67,6 +71,7 @@ import org.autojs.autojs.ui.edit.debug.DebugBar
import org.autojs.autojs.ui.edit.editor.CodeEditor import org.autojs.autojs.ui.edit.editor.CodeEditor
import org.autojs.autojs.ui.edit.editor.CodeEditor.CheckedPatternSyntaxException import org.autojs.autojs.ui.edit.editor.CodeEditor.CheckedPatternSyntaxException
import org.autojs.autojs.ui.edit.editor.JavaScriptHighlighter.MAX_HIGHLIGHT_CHARS import org.autojs.autojs.ui.edit.editor.JavaScriptHighlighter.MAX_HIGHLIGHT_CHARS
import org.autojs.autojs.ui.edit.editor.LayoutHelper
import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardHelper import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardHelper
import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView
import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView.ClickCallback import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView.ClickCallback
@@ -107,7 +112,6 @@ import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import kotlin.text.set
/** /**
* Created by Stardust on Sep 28, 2017. * Created by Stardust on Sep 28, 2017.
@@ -241,12 +245,24 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
private val mShowLoadingBarRunnable = Runnable { private val mShowLoadingBarRunnable = Runnable {
if (!mLoadingBarRequested) return@Runnable if (!mLoadingBarRequested) return@Runnable
if (mLoadingBarContainer.visibility == VISIBLE) return@Runnable if (mLoadingBarContainer.isVisible) return@Runnable
mLoadingBarContainer.visibility = VISIBLE mLoadingBarContainer.visibility = VISIBLE
mLoadingBarShownAtMs = SystemClock.uptimeMillis() mLoadingBarShownAtMs = SystemClock.uptimeMillis()
} }
// IME bottom inset last applied to editor padding.
// zh-CN: 最近一次应用到编辑器 padding 的 IME 底部值.
private var mLastImeBottomInset = 0
// Reusable rect for cursor visibility requests.
// zh-CN: 光标可见性请求复用 Rect.
private val mTmpCursorRect = Rect()
// Reusable rect for window visible area.
// zh-CN: 复用的窗口可见区域 Rect.
private val mTmpWindowVisibleRect = Rect()
constructor(context: Context) : super(context) constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@@ -257,6 +273,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
setUpEditor() setUpEditor()
setUpInputMethodEnhancedBar() setUpInputMethodEnhancedBar()
setUpFunctionsKeyboard() setUpFunctionsKeyboard()
setUpImeInsetsHandling()
setMenuItemStatus(R.id.save, false) setMenuItemStatus(R.id.save, false)
mDocsWebView.apply { mDocsWebView.apply {
webView.settings.displayZoomControls = true webView.settings.displayZoomControls = true
@@ -350,6 +367,112 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
} }
} }
private fun findHostActivityOrNull(): Activity? {
var c = context
while (c !is Activity && c is ContextWrapper) {
c = c.baseContext
}
return c as? Activity
}
private fun setUpImeInsetsHandling() {
// Handle IME insets to avoid keyboard covering the editor.
// zh-CN: 处理 IME inset, 避免软键盘遮挡编辑器内容.
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
// Compute the real overlap between editor and the visible window area.
// zh-CN: 计算 editor 与窗口可见区域之间的真实重叠量.
val targetBottom = if (imeVisible) {
val activity = findHostActivityOrNull()
if (activity != null) {
activity.window.decorView.getWindowVisibleDisplayFrame(mTmpWindowVisibleRect)
val loc = IntArray(2)
editor.getLocationOnScreen(loc)
val editorBottomOnScreen = loc[1] + editor.height
(editorBottomOnScreen - mTmpWindowVisibleRect.bottom).coerceAtLeast(0)
} else {
0
}
} else 0
if (targetBottom != mLastImeBottomInset) {
mLastImeBottomInset = targetBottom
// Apply bottom padding only when the editor is actually overlapped by IME.
// zh-CN: 仅当 editor 实际被 IME 遮挡时才应用底部 padding.
val v = editor
v.setPadding(v.paddingLeft, v.paddingTop, v.paddingRight, targetBottom)
// Only ensure cursor visible when IME becomes visible.
// zh-CN: 仅在 IME 变为可见时确保光标可见.
if (imeVisible) {
post { ensureCursorVisibleIfPossible() }
}
}
// Do not consume insets.
// zh-CN: 不消费 insets.
insets
}
}
private fun ensureCursorVisibleIfPossible() {
// Bring the caret into visible area when keyboard shows, without "scrolling back" on hide.
// zh-CN: 键盘弹起时将光标区域滚动到可见范围内, 键盘收起时不回滚.
val editText = editor.codeEditText
val textLen = editText.text?.length ?: 0
if (textLen <= 0) return
val layout = editText.layout ?: return
val sel = editText.selectionStart.coerceIn(0, textLen)
val line = LayoutHelper.getLineOfChar(layout, sel).coerceAtLeast(0)
val lineTop = layout.getLineTop(line)
val lineBottom = layout.getLineBottom(line)
// Compute a small rect around the current line/caret.
// zh-CN: 计算一个围绕当前行/光标的小矩形区域.
val x = runCatching { layout.getPrimaryHorizontal(sel).toInt() }.getOrElse { 0 }
val left = (x - editText.paddingLeft - 16).coerceAtLeast(0)
val right = left + 32
mTmpCursorRect.set(
/* left = */ left,
/* top = */ lineTop,
/* right = */ right,
/* bottom = */ lineBottom,
)
// Request rectangle visible inside scroll container.
// zh-CN: 请求在滚动容器内显示该矩形区域.
editText.requestRectangleOnScreen(mTmpCursorRect, true)
}
private fun ensureEditorHasCursorAfterLoadIfNeeded() {
// Ensure there is a visible caret after loading, so jump operations can work.
// zh-CN: 加载完成后确保存在可见光标, 以使跳转操作可用.
val editText = editor.codeEditText
val len = editText.text?.length ?: 0
if (len <= 0) return
// If selection is invalid or collapsed at -1-like state, normalize.
// zh-CN: 若 selection 异常, 则归一化.
val safeSel = editText.selectionStart.coerceIn(0, len)
// Request focus without forcing soft keyboard.
// zh-CN: 请求焦点但不强制弹出软键盘.
editText.requestFocus()
editText.isCursorVisible = true
editText.setSelection(safeSel)
// Optionally ensure visible once.
// zh-CN: 可选地确保一次可见.
ensureCursorVisibleIfPossible()
}
private fun setLoadingBar(loading: Boolean) { private fun setLoadingBar(loading: Boolean) {
if (loading) { if (loading) {
mLoadingBarRequested = true mLoadingBarRequested = true
@@ -430,7 +553,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
// Large file: show loading in enhance bar, allow viewing/scrolling (read-only). // Large file: show loading in enhance bar, allow viewing/scrolling (read-only).
// zh-CN: 大文件: 在增强栏显示加载提示, 允许查看/滚动(只读). // zh-CN: 大文件: 在增强栏显示加载提示, 允许查看/滚动(只读).
beginEditorLoadingUi(context.getString(R.string.text_loading_with_dots)) beginEditorLoadingUi(context.getString(R.string.text_loading_with_dots))
return loadUriStreamed(uri, totalBytesOrNull = sizeOrNull) return loadUriStreamed(uri)
} }
return Observable return Observable
@@ -508,7 +631,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
// Cancel loading before unregistering receivers to avoid ANR on exit. // Cancel loading before unregistering receivers to avoid ANR on exit.
// zh-CN: 退出前先取消加载, 避免返回退出时 ANR. // zh-CN: 退出前先取消加载, 避免返回退出时 ANR.
cancelLargeFileLoading("onDetachedFromWindow") cancelLargeFileLoading()
super.onDetachedFromWindow() super.onDetachedFromWindow()
context.unregisterReceiver(mOnRunFinishedReceiver) context.unregisterReceiver(mOnRunFinishedReceiver)
@@ -517,7 +640,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
// Cancel current large-file loading as soon as possible. // Cancel current large-file loading as soon as possible.
// zh-CN: 尽快取消当前大文件加载. // zh-CN: 尽快取消当前大文件加载.
private fun cancelLargeFileLoading(reason: String) { fun cancelLargeFileLoading() {
// Mark cancel first so both IO/UI can observe it. // Mark cancel first so both IO/UI can observe it.
// zh-CN: 先标记取消, 让 IO/UI 两侧都能观察到. // zh-CN: 先标记取消, 让 IO/UI 两侧都能观察到.
mLargeFileCancel.set(true) mLargeFileCancel.set(true)
@@ -553,7 +676,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
} }
} }
private fun loadUriStreamed(uri: Uri, totalBytesOrNull: Long?): Observable<String> { private fun loadUriStreamed(uri: Uri): Observable<String> {
val readBytes = AtomicLong(0L) val readBytes = AtomicLong(0L)
// Reset cancel state for a new session. // Reset cancel state for a new session.
@@ -561,7 +684,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
mLargeFileCancel.set(false) mLargeFileCancel.set(false)
return Observable return Observable
.create<String> { emitter -> .create { emitter ->
val resolver = context.contentResolver val resolver = context.contentResolver
// Emit an early placeholder so handleIntent() can proceed. // Emit an early placeholder so handleIntent() can proceed.
@@ -620,16 +743,12 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
.let { upstream -> .let { upstream ->
bridgeStreamToUiWithProgress( bridgeStreamToUiWithProgress(
upstream = upstream, upstream = upstream,
totalBytesOrNull = totalBytesOrNull,
readBytes = readBytes,
) )
} }
} }
private fun bridgeStreamToUiWithProgress( private fun bridgeStreamToUiWithProgress(
upstream: Observable<String>, upstream: Observable<String>,
totalBytesOrNull: Long?,
readBytes: AtomicLong,
): Observable<String> { ): Observable<String> {
val queue = ConcurrentLinkedQueue<String>() val queue = ConcurrentLinkedQueue<String>()
val done = AtomicBoolean(false) val done = AtomicBoolean(false)
@@ -669,7 +788,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { .doOnNext {
if (started.compareAndSet(false, true)) { if (started.compareAndSet(false, true)) {
startStreamingAppendWithProgress(queue, done, totalBytesOrNull, readBytes) startStreamingAppendWithProgress(queue, done)
} }
} }
} }
@@ -677,8 +796,6 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
private fun startStreamingAppendWithProgress( private fun startStreamingAppendWithProgress(
queue: ConcurrentLinkedQueue<String>, queue: ConcurrentLinkedQueue<String>,
done: AtomicBoolean, done: AtomicBoolean,
totalBytesOrNull: Long?,
readBytes: AtomicLong,
) { ) {
val editText = editor.codeEditText val editText = editor.codeEditText
@@ -733,6 +850,10 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
// zh-CN: 重要: 成功时必须结束加载 UI. // zh-CN: 重要: 成功时必须结束加载 UI.
endEditorLoadingUi() endEditorLoadingUi()
// Ensure caret/focus after loading is done.
// zh-CN: 加载结束后确保光标/焦点.
post { ensureEditorHasCursorAfterLoadIfNeeded() }
mLargeFileFrameCallback = null mLargeFileFrameCallback = null
return return
} }
@@ -812,127 +933,6 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
} }
} }
private fun bridgeStreamToUi(upstream: Observable<String>): Observable<String> {
val queue = ConcurrentLinkedQueue<String>()
val done = AtomicBoolean(false)
val started = AtomicBoolean(false)
// Subscribe on IO side, push chunks into queue.
// zh-CN: 在 IO 侧订阅, 将分片推入队列.
upstream
.observeOn(Schedulers.io())
.subscribe({ chunk ->
if (chunk.isNotEmpty()) {
queue.add(chunk)
} else {
// Ignore placeholder.
// zh-CN: 忽略占位分片.
}
}, { e ->
done.set(true)
post { editor.setProgress(false) }
e.printStackTrace()
}, {
done.set(true)
})
// Return a UI-side observable that triggers the append loop once.
// zh-CN: 返回 UI 侧 observable, 仅用于触发一次追加循环.
return Observable
.just("")
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
if (started.compareAndSet(false, true)) {
startStreamingAppend(queue, done)
}
}
}
private fun startStreamingAppend(queue: ConcurrentLinkedQueue<String>, done: AtomicBoolean) {
val editText = editor.codeEditText
// Large file streaming: disable all buttons while loading.
// zh-CN: 大文件流式加载: 加载期间禁用所有按钮.
mLargeFileMode = true
mEditorLoading = true
syncPrimaryMenuState()
// Keep a non-blocking indicator during streaming.
// zh-CN: 流式加载期间保持一个不遮挡操作的提示.
editor.setProgress(true, interactive = true)
editText.setLoadingText(true)
// Keep undo/redo disabled in large file mode.
// zh-CN: 大文件模式下保持撤销/重做禁用.
editor.setRedoUndoEnabled(false)
editText.setLoadingGutterDigits(7)
editText.setText("")
val targetMs = 6L
var chunkSize = 64 * 1024
val minChunkSize = 2 * 1024
val maxChunkSize = 256 * 1024
var lastAppendCostMs = 0L
val choreographer = Choreographer.getInstance()
val callback = object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
val finished = done.get() && queue.isEmpty()
if (finished) {
// Finish: restore state and close progress.
// zh-CN: 完成: 恢复状态并关闭进度.
editText.setLoadingText(false)
// Undo/redo stay disabled for large files.
// zh-CN: 大文件撤销/重做保持禁用.
editor.setRedoUndoEnabled(false)
editor.markUndoRedoBaselineAsUnchanged()
editor.setProgress(false)
mEditorLoading = false
syncPrimaryMenuState()
val len = editText.text?.length ?: 0
if (len in 1..MAX_HIGHLIGHT_CHARS) {
post { editor.refreshHighlightTokensIfAllowed() }
}
return
}
val sb = StringBuilder(chunkSize.coerceAtLeast(1024))
while (sb.length < chunkSize) {
val s = queue.poll() ?: break
sb.append(s)
}
if (sb.isNotEmpty()) {
val t0 = SystemClock.uptimeMillis()
editText.beginBatchEdit()
try {
editText.text?.append(sb)
} finally {
editText.endBatchEdit()
}
lastAppendCostMs = (SystemClock.uptimeMillis() - t0).coerceAtLeast(0L)
chunkSize = when {
lastAppendCostMs > targetMs * 2 -> (chunkSize / 2).coerceAtLeast(minChunkSize)
lastAppendCostMs < targetMs / 2 -> (chunkSize * 2).coerceAtMost(maxChunkSize)
else -> chunkSize
}
}
choreographer.postFrameCallback(this)
}
}
choreographer.postFrameCallback(callback)
}
private fun setInitialText(text: String) { private fun setInitialText(text: String) {
// NOTE: This method should not manage loading UI lifecycle anymore. // NOTE: This method should not manage loading UI lifecycle anymore.
// zh-CN: 注意: 此方法不再管理加载 UI 的生命周期. // zh-CN: 注意: 此方法不再管理加载 UI 的生命周期.
@@ -971,6 +971,10 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
editText.setLoadingText(false) editText.setLoadingText(false)
} }
// Ensure caret/focus after fast load.
// zh-CN: 快速加载完成后确保光标/焦点.
post { ensureEditorHasCursorAfterLoadIfNeeded() }
// Re-highlight once after fast load, but only when size is within highlighter limit. // Re-highlight once after fast load, but only when size is within highlighter limit.
// zh-CN: 快速加载完成后主动触发一次高亮, 但仅在文本大小不超过高亮上限时执行. // zh-CN: 快速加载完成后主动触发一次高亮, 但仅在文本大小不超过高亮上限时执行.
if (text.length <= MAX_HIGHLIGHT_CHARS) { if (text.length <= MAX_HIGHLIGHT_CHARS) {
@@ -1073,6 +1077,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
// Adapt chunk size based on how many chunks we managed to append in this frame. // Adapt chunk size based on how many chunks we managed to append in this frame.
// zh-CN: 根据本帧完成的分片数量自适应调整 chunkSize. // zh-CN: 根据本帧完成的分片数量自适应调整 chunkSize.
@Suppress("AssignedValueIsNeverRead")
chunkSize = when { chunkSize = when {
appendedChunks <= 1 -> (chunkSize / 2).coerceAtLeast(minChunkSize) appendedChunks <= 1 -> (chunkSize / 2).coerceAtLeast(minChunkSize)
appendedChunks >= 4 -> (chunkSize * 2).coerceAtMost(maxChunkSize) appendedChunks >= 4 -> (chunkSize * 2).coerceAtMost(maxChunkSize)
@@ -1553,7 +1558,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
} }
private fun buildSafeFileNameForSaveAs(baseName: String, ext: String): String { private fun buildSafeFileNameForSaveAs(baseName: String, ext: String): String {
val raw = if (baseName.isBlank()) "untitled" else baseName val raw = baseName.ifBlank { "untitled" }
val sanitized = raw.replace(Regex("""[\\/:*?"<>|]"""), "_") val sanitized = raw.replace(Regex("""[\\/:*?"<>|]"""), "_")
val niceExt = ext.trim().trimStart('.') val niceExt = ext.trim().trimStart('.')
return if (niceExt.isBlank()) sanitized else "$sanitized.$niceExt" return if (niceExt.isBlank()) sanitized else "$sanitized.$niceExt"
@@ -1929,7 +1934,7 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag
fun destroy() { fun destroy() {
// Cancel loading before destroying editor resources. // Cancel loading before destroying editor resources.
// zh-CN: 销毁前先取消加载, 再销毁编辑器相关资源. // zh-CN: 销毁前先取消加载, 再销毁编辑器相关资源.
cancelLargeFileLoading("destroy") cancelLargeFileLoading()
editor.destroy() editor.destroy()
mAutoCompletion?.shutdown() mAutoCompletion?.shutdown()

View File

@@ -1,5 +1,5 @@
#Sun Feb 08 13:21:45 CST 2026 #Sun Feb 08 15:05:14 CST 2026
BUILD_TIME=1770528105646 BUILD_TIME=1770534314495
COMPILE_SDK_VERSION=36 COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125 IMAGE_QUANT_NDK_VERSION=26.1.10909125
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=36 TARGET_SDK_VERSION=36
TARGET_SDK_VERSION_INRT=29 TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3713 VERSION_BUILD=3714
VERSION_NAME=6.7.0 Alpha19 VERSION_NAME=6.7.0 Alpha19
VSCODE_EXT_REQUIRED_VERSION=1.0.13 VSCODE_EXT_REQUIRED_VERSION=1.0.13