El arsenal de Android: formato de texto

IDE de ardilla es un editor de código multilingüe rápido y gratuito para Android.


Equipo de edición

  1. Grado de dependencia
  2. Los basicos
  3. Mas opciones
    1. Configuración
    2. Desplazamiento de texto
  4. Sugerencias de código
  5. Cancelar Restablecer
  6. Navegación
    1. Navegación de texto
    2. Encontrar y reemplazar
    3. Atajos
  7. Temas
  8. Complemento personalizado

Idiomas

  1. Grado de dependencia
  2. Idioma personalizado
    1. LanguageParser
    2. Proveedor de sugerencias
    3. Estilizador de lenguaje

los editorkit el módulo proporciona un editor de código sin soporte para lenguajes de programación.
Si está actualizando desde una versión anterior, eche un vistazo a guía de migración.
Tenga en cuenta que esta biblioteca solo es compatible con Kotlin.

Grado de dependencia

Agrega esto a tus formularios build.gradle expediente:

dependencies {
  ...
  implementation 'com.blacksquircle.ui:editorkit:2.1.2'
}

los editorkit módulo no proporciona soporte para el resaltado de sintaxis, debe agregar una dependencia de idioma específica. Puedes ver la lista de idiomas disponibles aquí.


Los basicos

Primero, debes agregar TextProcessor en su diseño:

<com.blacksquircle.ui.editorkit.widget.TextProcessor
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="top|start"
    android:id="@+id/editor" />

Segundo, usted debe proporcionar un Language objeto para admitir el resaltado de sintaxis usando el siguiente código:

val editor = findViewById<TextProcessor>(R.id.editor)

editor.language = JavaScriptLanguage() // or any other language you want

Terceratienes que llamar setTextContent para configurar el texto. No utilice la configuración predeterminada setText método.

editor.setTextContent("your code here")

También puede querer usar setTextContent(PrecomputedTextCompat) si está trabajando con archivos de texto grandes.

Por findespués de configurar el texto, debe borrar el historial de deshacer/rehacer porque no desea conservar el historial de modificaciones del archivo anterior:

import com.blacksquircle.ui.editorkit.model.UndoStack

editor.undoStack = UndoStack()
editor.redoStack = UndoStack()

Ahora puede comenzar a usar el editor de código.


Mas opciones

Configuración

Puede cambiar el comportamiento predeterminado del editor de código usando el complemento DSL como se muestra a continuación:

val pluginSupplier = PluginSupplier.create {
    pinchZoom { // whether the zoom gesture enabled
        minTextSize = 10f
        maxTextSize = 20f 
    }
    lineNumbers {
        lineNumbers = true // line numbers visibility
        highlightCurrentLine = true // whether the current line will be highlighted
    }
    highlightDelimiters() // highlight open/closed brackets beside the cursor
    autoIndentation {
        autoIndentLines = true // whether the auto indentation enabled
        autoCloseBrackets = true // automatically close open parenthesis/bracket/brace
        autoCloseQuotes = true // automatically close single/double quote when typing
    }
}
editor.plugins(pluginSupplier)

Para habilitar / deshabilitar complementos en tiempo de ejecución, rodee los métodos necesarios con if (enabled) { ... } operador:

val pluginSupplier = PluginSupplier.create {
    if (preferences.isLineNumbersEnabled) {
        lineNumbers()
    }
    if (preferences.isPinchZoomEnabled) {
        pinchZoom()
    }
    // ...
}
editor.plugins(pluginSupplier)

Recordar: cada vez que llamas editor.plugins(pluginSupplier) compare la lista de complementos actual con la nueva, luego separe los complementos que no existen en el archivo PluginSupplier.

Desplazamiento de texto

Para adjuntar el desplazador de texto, debe agregar TextScroller en el arreglo:

<com.blacksquircle.ui.editorkit.widget.TextScroller
    android:layout_width="30dp"
    android:layout_height="match_parent"
    android:id="@+id/scroller"
    app:thumbNormal="@drawable/fastscroll_normal"
    app:thumbDragging="@drawable/fastscroll_pressed"
    app:thumbTint="@color/blue" />

Ahora necesita pasar una referencia a una vista dentro attachTo método:

val editor = findViewById<TextProcessor>(R.id.editor)
val scroller = findViewById<TextScroller>(R.id.scroller)

scroller.attachTo(editor)

// or using Plugin DSL:

val pluginSupplier = PluginSupplier.create {
    ...
    textScroller {
        scroller = findViewById<TextScroller>(R.id.scroller)
    }
}

Sugerencias de código

Cuando trabaja con un editor de código, desea ver la lista de sugerencias de código. (Tenga en cuenta que debe proporcionar una Language objeto antes de empezar a usarlo).

Primerodebe crear un archivo de diseño que represente el elemento de sugerencia dentro del menú desplegable:

<!-- item_suggestion.xml -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:singleLine="true"
    android:padding="6dp"
    android:textSize="12sp"
    android:typeface="monospace"
    android:id="@+id/title" />

Segundonecesitas crear personalizado SuggestionAdapter:

class AutoCompleteAdapter(context: Context) : SuggestionAdapter(context, R.layout.item_suggestion) {

    override fun createViewHolder(parent: ViewGroup): SuggestionViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val view = inflater.inflate(R.layout.item_suggestion, parent, false)
        return AutoCompleteViewHolder(view)
    }
    
    class AutoCompleteViewHolder(itemView: View) : SuggestionViewHolder(itemView) {
    
        private val title: TextView = itemView.findViewById(R.id.title)
        
        override fun bind(suggestion: Suggestion?, query: String) {
            title.text = suggestion?.text
        }
    }
}

Tercerahabilite el complemento de finalización de código y configure SuggestionAdapter:

val pluginSupplier = PluginSupplier.create {
    ...
    codeCompletion {
        suggestionAdapter = AutoCompleteAdapter(this)
    }
}

UPD: Si tiene problemas con la posición de la ventana emergente (por ejemplo, desplazamiento vertical), esto podría resolverse configurando explícitamente Android: dropDownAnchor en XML.


Cancelar Restablecer

los TextProcessor soporta operaciones de deshacer/rehacer, pero recuerde que usted deber verifique la capacidad de deshacer/rehacer antes de llamar a los métodos reales:

// Undo
if (editor.canUndo()) {
    editor.undo()
}

// Redo
if (editor.canRedo()) {
    editor.redo()
}

También puede tener un caso de uso cuando desee actualizar la visibilidad de los botones Deshacer/Rehacer u otra interfaz de usuario después de que se hayan realizado los reemplazos de texto, esto se puede lograr agregando OnUndoRedoChangedListener:

editor.onUndoRedoChangedListener = object : OnUndoRedoChangedListener {
    override fun onUndoRedoChanged() {
        val canUndo = editor.canUndo()
        val canRedo = editor.canRedo()
        
        // ...
    }
}

Navegación

Navegación de texto

Puede utilizar estos métodos de extensión para navegar por el texto:

editor.moveCaretToStartOfLine()
editor.moveCaretToEndOfLine()
editor.moveCaretToPrevWord()
editor.moveCaretToNextWord()

… o utilice la función “Ir a la línea” para colocar el cursor en la línea específica:

import com.blacksquircle.ui.editorkit.exception.LineException

try {
    editor.gotoLine(lineNumber)
} catch (e: LineException) {
    Toast.makeText(this, "Line does not exists", Toast.LENGTH_SHORT).show()
}

Encontrar y reemplazar

los TextProcessor tiene soporte incorporado para operaciones de búsqueda y reemplazo, que incluyen:

  • Buscar hacia adelante o hacia atrás
  • Expresiones regulares
  • caja de fósforos
  • Solo palabras

La clase en sí contiene métodos que se explican por sí mismos para todas sus necesidades de búsqueda:

  • find(params) – Encuentra todos los resultados posibles en el texto con las opciones proporcionadas.
  • replaceFindResult(replaceText) – Encuentra la coincidencia actual y la reemplaza con texto nuevo.
  • replaceAllFindResults(replaceText) – Encuentra todas las coincidencias y reemplázalas con texto nuevo.
  • findNext() – Encuentra la siguiente coincidencia y desplázate hasta ella.
  • findPrevious() – Busque la coincidencia anterior y desplácese hasta ella.
  • clearFindResultSpans() – Borrar todos los rangos de búsqueda en la pantalla. Llame a este método cuando haya terminado de buscar.
import com.blacksquircle.ui.editorkit.model.FindParams

val params = FindParams(
    query = "function", // text to find
    regex = false, // regular expressions
    matchCase = true, // case sensitive
    wordsOnly = true // words only
)

editor.find(params)

// To navigate between results use findNext() and findPrevious()

Atajos

Si está usando el teclado bluetooth, probablemente querrá usar atajos de teclado para escribir su código más rápido. Para admitir atajos de teclado, debe habilitar el complemento de atajos y configurar OnShortcutListener:

val pluginSupplier = PluginSupplier.create {
    ...
    shortcuts {
        onShortcutListener = object : OnShortcutListener {
            override fun onShortcut(shortcut: Shortcut): Boolean {
                val (ctrl, shift, alt, keyCode) = shortcut
                return when {
                    ctrl && keyCode == KeyEvent.KEYCODE_DPAD_LEFT -> editor.moveCaretToStartOfLine()
                    ctrl && keyCode == KeyEvent.KEYCODE_DPAD_RIGHT -> editor.moveCaretToEndOfLine()
                    alt && keyCode == KeyEvent.KEYCODE_DPAD_LEFT -> editor.moveCaretToPrevWord()
                    alt && keyCode == KeyEvent.KEYCODE_DPAD_RIGHT -> editor.moveCaretToNextWord()
                    // ...
                    else -> false
                }
            }
        }
    }
}

los onShortcut el método se invocará solo si se presiona al menos una de las siguientes teclas: control, para cambiar, alternativa.
Es posible que ya haya notado que necesita devolver un Boolean valor como resultado de onShortcut método. Regreso true si el oyente ha consumido el evento de enlace, false de lo contrario.


Temas

los editorkit el módulo incluye algunos temas predefinidos en el archivo EditorTheme clase:

editor.colorScheme = EditorTheme.DARCULA // default

// or you can use one of these:
EditorTheme.MONOKAI
EditorTheme.OBSIDIAN
EditorTheme.LADIES_NIGHT
EditorTheme.TOMORROW_NIGHT
EditorTheme.VISUAL_STUDIO_2013

También puede escribir su propio tema editando el archivo ColorScheme propiedad. El siguiente ejemplo muestra cómo cargar mediante programación el esquema de color:

editor.colorScheme = ColorScheme(
    textColor = Color.parseColor("#C8C8C8"),
    backgroundColor = Color.parseColor("#232323"),
    gutterColor = Color.parseColor("#2C2C2C"),
    gutterDividerColor = Color.parseColor("#555555"),
    gutterCurrentLineNumberColor = Color.parseColor("#FFFFFF"),
    gutterTextColor = Color.parseColor("#C6C8C6"),
    selectedLineColor = Color.parseColor("#141414"),
    selectionColor = Color.parseColor("#454464"),
    suggestionQueryColor = Color.parseColor("#4F98F7"),
    findResultBackgroundColor = Color.parseColor("#1C3D6B"),
    delimiterBackgroundColor = Color.parseColor("#616161"),
    numberColor = Color.parseColor("#BACDAB"),
    operatorColor = Color.parseColor("#DCDCDC"),
    keywordColor = Color.parseColor("#669BD1"),
    typeColor = Color.parseColor("#669BD1"),
    langConstColor = Color.parseColor("#669BD1"),
    preprocessorColor = Color.parseColor("#C49594"),
    variableColor = Color.parseColor("#9DDDFF"),
    methodColor = Color.parseColor("#71C6B1"),
    stringColor = Color.parseColor("#CE9F89"),
    commentColor = Color.parseColor("#6BA455"),
    tagColor = Color.parseColor("#DCDCDC"),
    tagNameColor = Color.parseColor("#669BD1"),
    attrNameColor = Color.parseColor("#C8C8C8"),
    attrValueColor = Color.parseColor("#CE9F89"),
    entityRefColor = Color.parseColor("#BACDAB")
)

Complemento personalizado

Desde v2.1.0 el Equipo de edición la biblioteca admite la escritura de complementos personalizados para ampliar su funcionalidad predeterminada. Si está utilizando la última versión, es posible que esté familiarizado con PluginSupplier y saber usarlo es DSL. Ver Mas opciones para información.

Primero, necesitas crear una clase que amplíe el archivo EditorPlugin y proporcione su id en el constructor:

class CustomPlugin : EditorPlugin("custom-plugin-id") {

    var publicProperty = true

    override fun onAttached(editText: TextProcessor) {
        super.onAttached(editText)
        // TODO enable your feature here
    }
    
    override fun onDetached(editText: TextProcessor) {
        super.onDetached(editText)
        // TODO disable your feature here
    }
}

Segundo, puede anular los métodos del ciclo de vida, por ejemplo afterDrawinvocado inmediatamente después onDraw(Canvas) en el editor de código:

class CustomPlugin : EditorPlugin("custom-plugin-id") {
    
    var publicProperty = true
    
    private val dividerPaint = Paint().apply {
        color = Color.GRAY
    }

    override fun afterDraw(canvas: Canvas?) {
        super.afterDraw(canvas)
        if (publicProperty) {
            var i = editText.topVisibleLine
            while (i <= editText.bottomVisibleLine) {
                val startX = editText.paddingStart + editText.scrollX
                val startY = editText.paddingTop + editText.layout.getLineBottom(i)
                val stopX = editText.paddingLeft + editText.layout.width + editText.paddingRight
                val stopY = editText.paddingTop + editText.layout.getLineBottom(i)
                canvas?.drawLine( // draw divider for each visible line
                    startX.toFloat(), startY.toFloat(),
                    stopX.toFloat(), stopY.toFloat(),
                    dividerPaint
                )
                i++
            }
        }
    }
}

Tercera, crear una función de extensión para mejorar la legibilidad del código al agregar el complemento a PluginSupplier:

fun PluginSupplier.verticalDividers(block: CustomPlugin.() -> Unit = {}) {
    plugin(CustomPlugin(), block)
}

Por fin, puede conectar su complemento a través de DSL:

val pluginSupplier = PluginSupplier.create {
    verticalDividers {
        publicProperty = true // whether should draw the dividers
    }
    ...
}
editor.plugins(pluginSupplier)

Los módulos de lenguaje brindan soporte para lenguajes de programación. Esto incluye resaltado de sintaxis, sugerencias de código y analizador de código fuente. (Tenga en cuenta que el analizador de código fuente actualmente solo funciona en language-javascript módulo, pero pronto se implementará para más idiomas)

Grado de dependencia

Seleccione su idioma y agregue su dependencia a la de su módulo build.gradle expediente:

dependencies {
  ...
  implementation 'com.blacksquircle.ui:language-actionscript:2.1.2'
  implementation 'com.blacksquircle.ui:language-base:2.1.2' // for custom language
  implementation 'com.blacksquircle.ui:language-c:2.1.2'
  implementation 'com.blacksquircle.ui:language-cpp:2.1.2'
  implementation 'com.blacksquircle.ui:language-csharp:2.1.2'
  implementation 'com.blacksquircle.ui:language-groovy:2.1.2'
  implementation 'com.blacksquircle.ui:language-html:2.1.2'
  implementation 'com.blacksquircle.ui:language-java:2.1.2'
  implementation 'com.blacksquircle.ui:language-javascript:2.1.2'
  implementation 'com.blacksquircle.ui:language-json:2.1.2'
  implementation 'com.blacksquircle.ui:language-julia:2.1.2'
  implementation 'com.blacksquircle.ui:language-kotlin:2.1.2'
  implementation 'com.blacksquircle.ui:language-lisp:2.1.2'
  implementation 'com.blacksquircle.ui:language-lua:2.1.2'
  implementation 'com.blacksquircle.ui:language-markdown:2.1.2'
  implementation 'com.blacksquircle.ui:language-php:2.1.2'
  implementation 'com.blacksquircle.ui:language-plaintext:2.1.2'
  implementation 'com.blacksquircle.ui:language-python:2.1.2'
  implementation 'com.blacksquircle.ui:language-ruby:2.1.2'
  implementation 'com.blacksquircle.ui:language-shell:2.1.2'
  implementation 'com.blacksquircle.ui:language-sql:2.1.2'
  implementation 'com.blacksquircle.ui:language-typescript:2.1.2'
  implementation 'com.blacksquircle.ui:language-visualbasic:2.1.2'
  implementation 'com.blacksquircle.ui:language-xml:2.1.2'
}

Idioma personalizado

Primero, agrega esto a tu formulario build.gradle expediente:

dependencies {
  ...
  implementation 'com.blacksquircle.ui:language-base:2.1.2'
}

Segundo, implementar el Language interfaz:

import com.blacksquircle.ui.language.base.Language
import com.blacksquircle.ui.language.base.parser.LanguageParser
import com.blacksquircle.ui.language.base.provider.SuggestionProvider
import com.blacksquircle.ui.language.base.styler.LanguageStyler

class CustomLanguage : Language {

    override fun getName(): String {
        return "custom language"
    }

    override fun getParser(): LanguageParser {
        return CustomParser()
    }

    override fun getProvider(): SuggestionProvider {
        return CustomProvider()
    }

    override fun getStyler(): LanguageStyler {
        return CustomStyler()
    }
}

Cada idioma se compone de 3 componentes clave:

  1. LanguageParser se encarga de analizar el código fuente. El editor de código no usa este componente directamente.
  2. Proveedor de sugerencias es responsable de recopilar los nombres de funciones, campos y palabras clave dentro del alcance del archivo. El editor de código utiliza este componente para mostrar la lista de sugerencias de código.
  3. Estilizador de lenguaje es responsable del resaltado de sintaxis. El editor de código usa este componente para mostrar rangos de resaltado de sintaxis en la pantalla.

LanguageParser

LanguageParser es una interfaz que detecta errores de sintaxis para que puedas verlos en el archivo TextProcessor después.

Para crear un analizador personalizado, debe implementar execute método que devolverá un ParseResult.
Uno mismo ParseResult contiene una excepción significa que el código fuente no se puede compilar y contiene errores de sintaxis. Puede resaltar una línea de error llamando editor.setErrorLine(lineNumber) método.

Recordar que Tú no debe use este método en el hilo principal.

class CustomParser : LanguageParser {

    override fun execute(name: String, source: String): ParseResult {
        // TODO Implement parser
        val lineNumber = 0
        val columnNumber = 0
        val parseException = ParseException("describe exception here", lineNumber, columnNumber)
        return ParseResult(parseException)
    }
}

Proveedor de sugerencias

SuggestionProvider es una interfaz que proporciona sugerencias de código para mostrarlas en el archivo TextProcessor.

El texto se escanea por línea. Cuando el usuario cambia el código en una sola línea, esa línea es escaneada nuevamente por la actual SuggestionsProvider implementación, para que pueda mantener la lista de sugerencias actualizada. Esto se hace llamando al processLine método. Este método es responsable de analizar una línea de texto y guardar sugerencias de código para esa línea.

Después de llamar setTextContent el editor de código llamará processLine para cada línea para encontrar todas las sugerencias de código posibles.

class CustomProvider : SuggestionProvider {

    // You can use WordsManager
    // if you don't want to write the language-specific implementation
    private val wordsManager = WordsManager()

    override fun getAll(): Set<Suggestion> {
        return wordsManager.getWords()
    }

    override fun processLine(lineNumber: Int, text: String) {
        wordsManager.processLine(lineNumber, text)
    }

    override fun deleteLine(lineNumber: Int) {
        wordsManager.deleteLine(lineNumber)
    }

    override fun clearLines() {
        wordsManager.clearLines()
    }
}

Estilizador de lenguaje

LanguageStyler es una interfaz que proporciona rangos de resaltado de sintaxis para mostrarlos en el archivo TextProcessor.

los execute el método se ejecutará en el subproceso de fondo cada vez que cambie el texto. Puede usar expresiones regulares o lexer para encontrar palabras clave en el texto.

Recordar: cuantos más intervalos agregue, más tiempo llevará renderizar en el subproceso principal.

class CustomStyler : LanguageStyler {

    override fun execute(source: String, scheme: ColorScheme): List<SyntaxHighlightSpan> {
        val syntaxHighlightSpans = mutableListOf<SyntaxHighlightSpan>()
        
        // TODO Implement syntax highlighting
        
        return syntaxHighlightSpans
    }
}

Compruebe también

en vivo desde Droidcon, incluida la mayor actualización de Gemini en Android Studio y más lanzamientos del SDK de Android.

Acabamos de lanzar nuestro episodio de otoño de #TheAndroidShow en YouTube etcétera desarrollador.android.comy esta vez …

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *