M8Test Help

编辑器插件

18

开发编辑器插件步骤如下:

  1. 在 build.gradle.kts 文件中添加 m8test sdk 依赖 , 为了减小插件apk大小, 如果是 M8Test Version Catalog 中存在的依赖库请使用 compileOnly 来依赖项目

import com.m8test.util.VersionUtils plugins { alias(m8test.plugins.android.application) alias(m8test.plugins.kotlin.android) alias(m8test.plugins.kotlin.compose) } android { namespace = "com.m8test.editor.language" compileSdk = m8test.versions.compileSdk.get().toInt() defaultConfig { minSdk = m8test.versions.minSdk.get().toInt() targetSdk = m8test.versions.targetSdk.get().toInt() versionName = libs.versions.versionName.get() versionCode = VersionUtils.getCode(versionName!!) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.toVersion(m8test.versions.sourceCompatibility.get()) targetCompatibility = JavaVersion.toVersion(m8test.versions.targetCompatibility.get()) } kotlinOptions { jvmTarget = m8test.versions.jvmTarget.get() } buildFeatures { compose = true } } dependencies { compileOnly(platform(m8test.androidx.compose.bom)) compileOnly(m8test.bundles.compose) compileOnly(m8test.m8test.sdk) compileOnly(m8test.gson) implementation(libs.m8test.compose.widget) implementation(libs.bundles.editor.kit) }
  1. 编写插件继承 AbstractComposableEditorPlugin 并重写 Content 方法

package com.m8test.editor.language import android.content.res.Resources import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp import com.blacksquircle.ui.language.css.CssLanguage import com.blacksquircle.ui.language.groovy.GroovyLanguage import com.blacksquircle.ui.language.html.HtmlLanguage import com.blacksquircle.ui.language.ini.IniLanguage import com.blacksquircle.ui.language.java.JavaLanguage import com.blacksquircle.ui.language.javascript.JavaScriptLanguage import com.blacksquircle.ui.language.json.JsonLanguage import com.blacksquircle.ui.language.kotlin.KotlinLanguage import com.blacksquircle.ui.language.lua.LuaLanguage import com.blacksquircle.ui.language.markdown.MarkdownLanguage import com.blacksquircle.ui.language.php.PhpLanguage import com.blacksquircle.ui.language.python.PythonLanguage import com.blacksquircle.ui.language.ruby.RubyLanguage import com.blacksquircle.ui.language.toml.TomlLanguage import com.blacksquircle.ui.language.typescript.TypeScriptLanguage import com.blacksquircle.ui.language.xml.XmlLanguage import com.blacksquircle.ui.language.yaml.YamlLanguage import com.google.accompanist.imageloading.rememberDrawablePainter import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.m8test.compose.widget.AutoResizeSurfaceDialog import com.m8test.compose.widget.BackTopBarScaffold import com.m8test.editor.plugin.AbstractComposableEditorPlugin import com.m8test.editor.ui.Editor import com.m8test.plugin.api.ApkPluginProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch /** * Description TODO * * @date 2024/12/18 15:43:44 * @author M8Test, [email protected], https://m8test.com */ class LanguageEditorPlugin(apkPluginProvider: ApkPluginProvider) : AbstractComposableEditorPlugin(apkPluginProvider = apkPluginProvider) { private val gson = Gson() private val defaultLanguages = listOf( LanguageWrapper(language = "groovy", extensions = mutableSetOf("groovy")), LanguageWrapper(language = "java", extensions = mutableSetOf("java", "javas")), LanguageWrapper(language = "javascript", extensions = mutableSetOf("js")), LanguageWrapper(language = "kotlin", extensions = mutableSetOf("kt", "kts")), LanguageWrapper(language = "lua", extensions = mutableSetOf("lua")), LanguageWrapper(language = "php", extensions = mutableSetOf("php")), LanguageWrapper(language = "python", extensions = mutableSetOf("py")), LanguageWrapper(language = "ruby", extensions = mutableSetOf("rb")), LanguageWrapper(language = "yaml", extensions = mutableSetOf("yaml", "yml")), LanguageWrapper(language = "json", extensions = mutableSetOf("json")), LanguageWrapper(language = "xml", extensions = mutableSetOf("xml")), LanguageWrapper(language = "typescript", extensions = mutableSetOf("ts")), LanguageWrapper(language = "markdown", extensions = mutableSetOf("md")), LanguageWrapper(language = "css", extensions = mutableSetOf("css")), LanguageWrapper(language = "html", extensions = mutableSetOf("html")), LanguageWrapper(language = "ini", extensions = mutableSetOf("ini")), LanguageWrapper(language = "toml", extensions = mutableSetOf("toml")) ) private val languages = listOf( GroovyLanguage(), JavaLanguage(), JavaScriptLanguage(), KotlinLanguage(), LuaLanguage(), PhpLanguage(), PythonLanguage(), RubyLanguage(), YamlLanguage(), JsonLanguage(), XmlLanguage(), TypeScriptLanguage(), MarkdownLanguage(), CssLanguage(), HtmlLanguage(), IniLanguage(), TomlLanguage(), ) @Composable private fun RestoreDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) { AutoResizeSurfaceDialog( onDismissRequest = onDismiss, shape = RoundedCornerShape(20.dp), content = { Column( modifier = Modifier.padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { val r = [email protected]() Text( text = r.getString(R.string.text_warn), style = MaterialTheme.typography.titleLarge ) Text(text = r.getString(R.string.text_restore_language_warning)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { TextButton(onClick = onDismiss) { Text(text = r.getString(R.string.text_cancel)) } TextButton(onClick = { onDismiss() onConfirm() }) { Text(text = r.getString(R.string.text_confirm)) } } } }) } @Composable override fun Content() { val languages = remember { mutableStateListOf<LanguageWrapper>().apply { addAll(getLanguagesFromSetting()) } } val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher var canSave by remember { mutableStateOf(false) } var showRestoreDialog by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() if (showRestoreDialog) { RestoreDialog(onConfirm = { getSettings().remove("languages") scope.launch(Dispatchers.IO) { languages.clear() delay(100) languages.addAll(getLanguagesFromSetting()) } }) { showRestoreDialog = false } } BackTopBarScaffold( onBackPress = { onBackPressedDispatcher?.onBackPressed() }, titleText = [email protected]() .getString(R.string.text_editor_language_setting), actions = { IconButton(enabled = canSave, onClick = { getSettings().set("languages", gson.toJson(languages)) canSave = false }) { Icon( painter = painterResource( [email protected](), R.drawable.ic_baseline_save_24 ), contentDescription = null ) } IconButton(onClick = { showRestoreDialog = true }) { Icon( painter = painterResource( resources = [email protected](), R.drawable.ic_baseline_restore_24 ), contentDescription = null ) } }) { paddingValues -> LazyColumn( modifier = Modifier .fillMaxSize() .padding(paddingValues) ) { items(languages) { language -> LanguageItem( language = language, onValueChange = { canSave = true }, modifier = Modifier.fillMaxWidth() ) } } } } private fun getLanguagesFromSetting(): List<LanguageWrapper> { return getSettings().get("languages") ?.let { gson.fromJson(it, object : TypeToken<List<LanguageWrapper>>() {}.type) } ?: defaultLanguages.map { LanguageWrapper( language = it.language, extensions = mutableSetOf<String>().apply { addAll(it.extensions) } ) } } @Composable private fun LanguageItem( language: LanguageWrapper, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, ) { Row(modifier = modifier) { var value by remember { mutableStateOf(language.extensions.joinToString()) } OutlinedTextField( modifier = Modifier.fillMaxWidth(), label = { Text(text = language.language) }, placeholder = { val r = [email protected]() Text(text = r.getString(R.string.text_extension_placeholder)) }, value = value, onValueChange = { value = it language.extensions.clear() language.extensions.addAll(value.split(",").map { it.trim() } .filter { it.isNotBlank() }) onValueChange(it) }) } } override fun onAttached(editor: Editor) { super.onAttached(editor) val settingLanguages = getLanguagesFromSetting() editor.language = editor.file.extension.let { extension -> languages.firstOrNull { l -> settingLanguages.firstOrNull { it.language == l.languageName } ?.extensions?.contains(extension) == true } } ?: editor.scriptLanguage?.getName()?.lowercase() ?.let { name -> languages.firstOrNull { it.languageName == name } } } } @Composable private fun painterResource(resources: Resources, id: Int): Painter { return rememberDrawablePainter(drawable = resources.getDrawable(id, null)) }
  1. 在 AndroidManifest.xml 中配置插件信息

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application android:icon="@drawable/ic_launcher"> <meta-data android:name="com.m8test.plugin.description" android:value="本插件实现了为脚本编辑器添加编程语言语法高亮显示的功能." /> <meta-data android:name="com.m8test.plugin.url" android:value="https://github.com/m8test/Plugins" /> <meta-data android:name="com.m8test.plugin.type" android:value="editor" /> <meta-data android:name="com.m8test.plugin.name" android:value="language-editor" /> <meta-data android:name="com.m8test.plugin.className" android:value="com.m8test.editor.language.LanguageEditorPlugin" /> </application> </manifest>
  • com.m8test.plugin.type: 插件类型, 此处为 common

  • com.m8test.plugin.name: 插件名称, 可以为任意字符串

  • com.m8test.plugin.className: 实现了插件的全类名

Last modified: 29 April 2025