Skip to content

hossain-khan/android-syntax-highlighter

Repository files navigation

android-syntax-highlighter

yet NOT another android syntax highlighter (YNAASH)

Objective

Explore well established web based syntax highlighter like PrismJS and highlight.js, and showcase how anybody can quickly incorporate these into their project by following some examples provided here.

The intention is NOT to create another library project that gets abandoned over time. Feel free to copy parts of code that is necessary for you to add syntax highlighting support to your app.

  • Try out the latest release - syntax-highlighter-example-v1.1.apk (2.5 MB) [Download]

Existing Syntax Highlighting Libraries

If you need a library, you may look into following existing projects

  1. CodeView-Android - Display code with syntax highlighting ✨ in native way.
    845 ⭐, Last updated: Jan 24, 2019
  2. highlightjs-android - A view for source code syntax highlighting on Android.
    310 ⭐, Last updated: Aug 19, 2020
  3. Syntax-View-Android - Beautiful Android Syntax View with line counter it will automatically highlight the code.
    56 ⭐, Last updated: Mar 24, 2020
  4. KodeEditor - A simple code editor with syntax highlighting and pinch to zoom.
    72 ⭐, Last updated: May 19, 2023
  5. HighlightJs View - Android - A view for source code syntax highlighting on Android.
    310 ⭐, Last updated: Aug 19, 2020
  6. synta kt s - Simple to use text parser and syntax highlighter for Kotlin Multiplatform.
    7 ⭐, Last updated: Nov 11, 2023 (Actively beind developed with KMP focus)

NOTE: The 'Last updated' and ⭐ data was taken as of Nov 13th, 2023


Under the hood

Here is how you would have syntax highlighting using any modern JavaScript library.

ps. I also ✍️ wrote a short blog summarizing the process on Medium.com

1. Choose JS Library

There are several popular syntax highlighters. Here I have used Prism JS because it's light weight and one of the popular one.

Follow their documentation to download the library with the plugins you need. For example, showing line number is a plugin, that is how they can keep the library so light weight like 2KB core.

2. Use HTML+CSS+JS Asset

Move downloaded prism.js and prism.css to assets resource directory. For example, here I have moved them to "assets/www" folder.

Write plain HTML that loads these assets and your source code.

For example:

<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="www/prism.css" rel="stylesheet"/>
        <script src="www/prism.js"></script>
    </head>
    <body>
        <h1>Demo Syntax Highlight</h1>
        <p>Description about the code.</p>
        <pre class="line-numbers">
        <code class="language-kotlin">class MainActivity : AppCompatActivity() { /* ... */ }
        </code>
        </pre>
    </body>
</html>

NOTE: For most cases, hard coding sample code for each sample-code is not ideal. Soon, we will explore how to make the HTML file as template and inject source code from Activity or Fragment. See Custom View section below for detailed instructions.

3. Load the static HTML on WebView

Finally on your Activity or Fragment, once view is loaded initialize WebView with local html file from assets.

webView.apply {
    settings.javaScriptEnabled = true
    webChromeClient = WebViewChromeClient()
    webViewClient = AppWebViewClient()
    loadUrl("file:///android_asset/code-highlight.html")
}

Screenshot

Here is a screenshot taken from a demo static html page that has syntax highlighting using Prism JS.

device-2020-07-18-092715 device-2020-07-18-092727 device-2020-07-18-092736

Building your own Fragment or Custom View

Ideally, there should be a modular component or custom-view that you re-use syntax highlighting with dynamic content. For that having a Fragment or custom View is ideal.

We can taken the learning from above to wrap the JavaScript based syntax highlighting library in fragment or custom view using WebView. Both comes with advantage of it's own. Regardless if which option is chosen, the underlying code is almost identical.

Custom View

The advantage of custom view is that, it can be used anywhere, Activity or Fragment. Let's take a look how we can templatize the HTML to load source code dynamically.

In this case, all we need to do is move the html content defined above to a String variable with options you need.

PrismJS Template Function

fun prismJsHtmlContent(
    formattedSourceCode: String,
    language: String,
    showLineNumbers: Boolean = true
): String {
    return """<!DOCTYPE html>
<html>
<head>
    <!-- https://developer.chrome.com/multidevice/webview/pixelperfect -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="www/main.css" rel="stylesheet"/>

    <!-- https://prismjs.com/ -->
    <link href="www/prism.css" rel="stylesheet"/>
    <script src="www/prism.js"></script>
</head>
<body>
<pre class="${if (showLineNumbers) "line-numbers" else ""}">
<code class="language-${language}">${formattedSourceCode}</code>
</pre>
</body>
</html>
"""
}

In this example, we have showLineNumbers as optional parameter, likewise we could have line number parameter to highlight a line or section. PrismJS has dozens of plugins that you can use and expose those options though this function.

Creating custom syntax highlighter WebView

Here you just need to extend the WebView and expose a function bindSyntaxHighlighter() to send source code and configurations.

class SyntaxHighlighterWebView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {
    companion object {
        private const val ANDROID_ASSETS_PATH = "file:///android_asset/"
    }

    @SuppressLint("SetJavaScriptEnabled")
    fun bindSyntaxHighlighter(
        formattedSourceCode: String,
        language: String,
        showLineNumbers: Boolean = false
    ) {
        settings.javaScriptEnabled = true
        webChromeClient = WebViewChromeClient()
        webViewClient = AppWebViewClient()

        loadDataWithBaseURL(
            ANDROID_ASSETS_PATH /* baseUrl */,
            prismJsHtmlContent(formattedSourceCode, language, showLineNumbers) /* html-data */,
            "text/html" /* mimeType */,
            "utf-8" /* encoding */,
            "" /* failUrl */
        )
    }
}

Use custom view from Fragment or Activity

Using the newly defined SyntaxHighlighterWebView in Fragment or Activity is business as usual that you are used to.

In your Layout XML file, add the view with proper layout parameters.

<your.prismjs.SyntaxHighlighterWebView
    android:id="@+id/syntax_highlighter_webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

When Fragment or Activity is loaded, get reference to SyntaxHighlighterWebView and bind it with source code.

val syntaxHighlighter = findViewById(R.id.syntax_highlighter_webview)

syntaxHighlighter.bindSyntaxHighlighter(
    formattedSourceCode = "data class Student(val name: String)",
    language = "kotlin"
)

That's it, you have re-usable custom view that you can use anywhere in the layout.

Fragment

Fragment is a modular UI component that can be use in a Activity. It comes with it's own flexibility like:

  • Being able to replace whole screen content
  • Replace only part of the content
  • Use with navigation library as destination
  • and so on

In this case Fragment is just a shell that loads WebView and configures it such that it can show syntax highlighted source code.

Create custom Syntax Highlighter Fragment

Based on Android official guide we need to pass all the required data needed for prismJsHtmlContent function defined above

fun newInstance(
    formattedSourceCode: String,
    language: String,
    showLineNumbers: Boolean = false
) = SyntaxHighlighterFragment().apply {
    arguments = Bundle().apply {
        putString(ARG_KEY_SOURCE_CODE_CONTENT, formattedSourceCode)
        putString(ARG_KEY_CODE_LANGUAGE, language)
        putBoolean(ARG_KEY_SHOW_LINE_NUMBERS, showLineNumbers)
    }
}

See SyntaxHighlighterFragment.kt source code for full example.

And finally when Fragment#onViewCreated() is called, we use the extracted the bundle parameters to initialize and load the syntax highlighting.

@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    // Loads the plain `WebView` defined in `fragment_highlighter.xml`
    val webView: WebView = view.findViewById(R.id.web_view)

    webView.apply {
        settings.javaScriptEnabled = true
        webChromeClient = WebViewChromeClient()
        webViewClient = AppWebViewClient()

        loadDataWithBaseURL(
            ANDROID_ASSETS_PATH /* baseUrl */,
            prismJsHtmlContent(sourceCode, language, showLineNumbers) /* html-data */,
            "text/html" /* mimeType */,
            "utf-8" /* encoding */,
            "" /* failUrl */
        )
    }
}

Using the Syntax Highlighter Fragment

From your Activity or Fragment, create an instance of SyntaxHighlighterFragment and add that to fragment container on the screen.

val fragment = SyntaxHighlighterFragment.newInstance(
    formattedSourceCode = "data class Student(val name: String)",
    language = "kotlin",
    showLineNumbers = true
)

See PrismJsDemoActivity.kt source code for full example.