yet NOT another android syntax highlighter (YNAASH)
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.
If you need a library, you may look into following existing projects
- CodeView-Android - Display code with syntax highlighting ✨ in native way.
845 ⭐, Last updated: Jan 24, 2019 - highlightjs-android - A view for source code syntax highlighting on Android.
310 ⭐, Last updated: Aug 19, 2020 - Syntax-View-Android - Beautiful Android Syntax View with line counter it will automatically highlight the code.
56 ⭐, Last updated: Mar 24, 2020 - KodeEditor - A simple code editor with syntax highlighting and pinch to zoom.
72 ⭐, Last updated: May 19, 2023 - HighlightJs View - Android - A view for source code syntax highlighting on Android.
310 ⭐, Last updated: Aug 19, 2020 - 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
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
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.
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.
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")
}
Here is a screenshot taken from a demo static html page that has syntax highlighting using Prism JS.
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.
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.
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.
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 */
)
}
}
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
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.
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 */
)
}
}
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.