The Android Arsenal – Textual content Formatting


Squircle IDE is a quick and free multi-language code editor for Android.


  1. Gradle Dependency
  2. The Fundamentals
  3. Extra Choices
    1. Configuration
    2. Textual content Scroller
  4. Code Options
  5. Undo Redo
  6. Navigation
    1. Textual content Navigation
    2. Discover and Change
    3. Shortcuts
  7. Theming
  8. Customized Plugin


  1. Gradle Dependency
  2. Customized Language
    1. LanguageParser
    2. SuggestionProvider
    3. LanguageStyler

The editorkit module gives code editor with none assist for programming languages.
If you’re upgrading from any older model, please take a look on the migration information.
Please be aware that this library solely helps Kotlin.

Gradle Dependency

Add this to your module’s construct.gradle file:

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

The editorkit module doesn’t present assist for syntax highlighting, you have to add particular language dependency. You possibly can see checklist of obtainable languages right here.

The Fundamentals

First, you have to add TextProcessor in your format:

    android:id="@+id/editor" />

Second, you have to present a Language object to assist syntax highlighting by utilizing following code:

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

editor.language = JavaScriptLanguage() // or some other language you need

Third, you have to name setTextContent to set the textual content. Do not use the default setText methodology.

editor.setTextContent("your code right here")

Additionally you may need to use setTextContent(PrecomputedTextCompat) should you’re working with giant textual content information.

Lastly, after you set the textual content you have to clear undo/redo historical past since you do not need to maintain the change historical past of earlier file:

import com.blacksquircle.ui.editorkit.mannequin.UndoStack

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

Now you may start utilizing the code editor.

Extra Choices


You possibly can change the default code editor conduct by utilizing Plugin DSL as proven beneath:

val pluginSupplier = PluginSupplier.create {
    pinchZoom { // whether or not the zoom gesture enabled
        minTextSize = 10f
        maxTextSize = 20f 
    lineNumbers {
        lineNumbers = true // line numbers visibility
        highlightCurrentLine = true // whether or not the present line shall be highlighted
    highlightDelimiters() // spotlight open/closed brackets beside the cursor
    autoIndentation {
        autoIndentLines = true // whether or not the auto indentation enabled
        autoCloseBrackets = true // routinely shut open parenthesis/bracket/brace
        autoCloseQuotes = true // routinely shut single/double quote when typing

To allow/disable plugins in runtime, encompass vital strategies with if (enabled) { ... } operator:

val pluginSupplier = PluginSupplier.create {
    if (preferences.isLineNumbersEnabled) {
    if (preferences.isPinchZoomEnabled) {
    // ...

Keep in mind: everytime you name editor.plugins(pluginSupplier) it compares present plugin checklist with the brand new one, after which detaches plugins that does not exists within the PluginSupplier.

Textual content Scroller

To connect the textual content scroller you have to add TextScroller in format:

    app:thumbTint="@shade/blue" />

Now you have to go a reference to a view inside attachTo methodology:

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


// or utilizing Plugin DSL:

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

Code Options

Whenever you working with a code editor you need to see the checklist of code suggestion. (Notice that it’s important to present a Language object earlier than begin utilizing it.)

First, you have to create a format file that can symbolize the suggestion merchandise inside dropdown menu:

<!-- item_suggestion.xml -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/title" />

Second, you have to create {custom} SuggestionAdapter:

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

    override enjoyable createViewHolder(mother or father: ViewGroup): SuggestionViewHolder {
        val inflater = LayoutInflater.from(mother or father.context)
        val view = inflater.inflate(R.format.item_suggestion, mother or father, false)
        return AutoCompleteViewHolder(view)
    class AutoCompleteViewHolder(itemView: View) : SuggestionViewHolder(itemView) {
        non-public val title: TextView = itemView.findViewById(R.id.title)
        override enjoyable bind(suggestion: Suggestion?, question: String) {
            title.textual content = suggestion?.textual content

Third, allow the code completion plugin and set SuggestionAdapter:

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

UPD: When you having an points with the popup place (e.g vertical offset), this is likely to be solved by explicitly setting android:dropDownAnchor in XML.

Undo Redo

The TextProcessor helps undo/redo operations, however keep in mind that you should verify the power to undo/redo earlier than calling precise strategies:

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

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

Additionally you could have a use case while you need to replace undo/redo buttons visibility or different UI after the textual content replacements is finished, this may be achieved by including OnUndoRedoChangedListener:

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


Textual content Navigation

You should utilize these extension strategies to navigate in textual content:


…or use «Go to Line» characteristic to put the caret on the particular line:

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

attempt {
} catch (e: LineException) {
    Toast.makeText(this, "Line doesn't exists", Toast.LENGTH_SHORT).present()

Discover and Change

The TextProcessor has built-in assist for search and change operations, together with:

  • Search ahead or backward
  • Common Expressions
  • Match Case
  • Phrases Solely

The category itself comprises self-explanatory strategies for all of your looking out wants:

  • discover(params) – Discover all attainable leads to textual content with supplied choices.
  • replaceFindResult(replaceText) – Finds present match and replaces it with new textual content.
  • replaceAllFindResults(replaceText) – Finds all matches and replaces them with the brand new textual content.
  • findNext() – Finds the following match and scrolls to it.
  • findPrevious() – Finds the earlier match and scrolls to it.
  • clearFindResultSpans() – Clears all discover spans on the display. Name this methodology while you’re accomplished looking out.
import com.blacksquircle.ui.editorkit.mannequin.FindParams

val params = FindParams(
    question = "perform", // textual content to seek out
    regex = false, // common expressions
    matchCase = true, // case delicate
    wordsOnly = true // phrases solely


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


When you’re utilizing bluetooth keyboard you most likely need to use keyboard shortcuts to write down your code quicker. To assist the keyboard shortcuts you have to allow the shortcuts plugin and set OnShortcutListener:

val pluginSupplier = PluginSupplier.create {
    shortcuts {
        onShortcutListener = object : OnShortcutListener {
            override enjoyable 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

The onShortcut methodology shall be invoked provided that at the least one in all following keys is pressed: ctrl, shift, alt.
You may already observed that it’s important to return a Boolean worth as the results of onShortcut methodology. Return true if the listener has consumed the shortcut occasion, false in any other case.


The editorkit module contains some default themes within the EditorTheme class:

editor.colorScheme = EditorTheme.DARCULA // default

// or you should utilize one in all these:

You too can write your individual theme by altering the ColorScheme properties. The instance beneath reveals how one can programmatically load the colour scheme:

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

Customized Plugin

Since v2.1.0 the EditorKit library helps writing {custom} plugins to increase it is default performance. When you’re utilizing the newest model, you is likely to be acquainted with PluginSupplier and know find out how to use it is DSL. See Extra Choices for information.

First, you have to create a category which extends the EditorPlugin and supply it is id within the constructor:

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

    var publicProperty = true

    override enjoyable onAttached(editText: TextProcessor) {
        // TODO allow your characteristic right here
    override enjoyable onDetached(editText: TextProcessor) {
        // TODO disable your characteristic right here

Second, you may override lifecycle strategies, for instance afterDraw, which invoked instantly after onDraw(Canvas) in code editor:

class CustomPlugin : EditorPlugin("custom-plugin-id") {
    var publicProperty = true
    non-public val dividerPaint = Paint().apply {
        shade = Coloration.GRAY

    override enjoyable afterDraw(canvas: Canvas?) {
        if (publicProperty) {
            var i = editText.topVisibleLine
            whereas (i <= editText.bottomVisibleLine) {
                val startX = editText.paddingStart + editText.scrollX
                val startY = editText.paddingTop + editText.format.getLineBottom(i)
                val stopX = editText.paddingLeft + editText.format.width + editText.paddingRight
                val stopY = editText.paddingTop + editText.format.getLineBottom(i)
                canvas?.drawLine( // draw divider for every seen line
                    startX.toFloat(), startY.toFloat(),
                    stopX.toFloat(), stopY.toFloat(),

Third, create an extension perform to enhance code readability when including your plugin to a PluginSupplier:

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

Lastly, you may connect your plugin utilizing DSL:

val pluginSupplier = PluginSupplier.create {
    verticalDividers {
        publicProperty = true // whether or not ought to draw the dividers

The language modules gives assist for programming languages. This contains syntax highlighting, code strategies and supply code parser. (Notice that supply code parser at the moment works solely in language-javascript module, however will probably be applied for extra languages quickly)

Gradle Dependency

Choose your language and add it is dependency to your module’s construct.gradle file:

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'

Customized Language

First, add this to your module’s construct.gradle file:

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

Second, implement the Language interface:

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

class CustomLanguage : Language {

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

    override enjoyable getParser(): LanguageParser {
        return CustomParser()

    override enjoyable getProvider(): SuggestionProvider {
        return CustomProvider()

    override enjoyable getStyler(): LanguageStyler {
        return CustomStyler()

Each language consist of three key elements:

  1. LanguageParser is accountable for analyzing the supply code. The code editor doesn’t use this element immediately.
  2. SuggestionProvider is accountable for amassing the names of features, fields, and key phrases inside your file scope. The code editor use this element to show the checklist of code strategies.
  3. LanguageStyler is accountable for syntax highlighting. The code editor use this element to show syntax spotlight spans on the display.


LanguageParser is an interface which detects syntax errors so you may show them within the TextProcessor later.

To create a {custom} parser you have to implement execute methodology that can return a ParseResult.
If ParseResult comprises an exception it signifies that the supply code cannot compile and comprises syntax errors. You possibly can spotlight an error line by calling editor.setErrorLine(lineNumber) methodology.

Keep in mind that you just should not use this methodology on the principle thread.

class CustomParser : LanguageParser {

    override enjoyable execute(identify: String, supply: String): ParseResult {
        // TODO Implement parser
        val lineNumber = 0
        val columnNumber = 0
        val parseException = ParseException("describe exception right here", lineNumber, columnNumber)
        return ParseResult(parseException)


SuggestionProvider is an interface which gives code strategies to show them within the TextProcessor.

The textual content scanning is finished on a per-line foundation. When the person edits code on a single line, that line is re-scanned by the present SuggestionsProvider implementation, so you may maintain your strategies checklist updated. That is accomplished by calling the processLine methodology. This methodology is accountable for parsing a line of textual content and saving the code strategies for that line.

After calling setTextContent the code editor will name processLine for every line to seek out all attainable code strategies.

class CustomProvider : SuggestionProvider {

    // You should utilize WordsManager
    // should you do not need to write the language-specific implementation
    non-public val wordsManager = WordsManager()

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

    override enjoyable processLine(lineNumber: Int, textual content: String) {
        wordsManager.processLine(lineNumber, textual content)

    override enjoyable deleteLine(lineNumber: Int) {

    override enjoyable clearLines() {


LanguageStyler is an interface which gives syntax spotlight spans to show them within the TextProcessor.

The execute methodology shall be executed on the background thread each time the textual content adjustments. You should utilize regex or lexer to seek out the key phrases in textual content.

Keep in mind: the extra spans you add, the extra time it takes to render on the principle thread.

class CustomStyler : LanguageStyler {

    override enjoyable execute(supply: String, scheme: ColorScheme): Listing<SyntaxHighlightSpan> {
        val syntaxHighlightSpans = mutableListOf<SyntaxHighlightSpan>()
        // TODO Implement syntax highlighting
        return syntaxHighlightSpans



Leave a Comment