diff --git a/README.md b/README.md index b44d250..f614e2b 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,16 @@ [![Release](https://jitpack.io/v/softwee/codeview-android.svg)](https://jitpack.io/#softwee/codeview-android) [![Build Status](https://travis-ci.org/Softwee/codeview-android.svg?branch=master)](https://travis-ci.org/Softwee/codeview-android) -CodeView helps to show code content with syntax highlighting in native way. +CodeView helps to show code content with syntax highlighting in native way. ## Description -CodeView contains 3 core parts to implement necessary logic:
+CodeView contains 3 core parts to implement necessary logic:
-1. CodeClassifier is trying to define what language presented in code snippet. It built upon [Naive Bayes classifier](https://github.com/ptnplanet/Java-Naive-Bayes-Classifier). There is no need to work with this class directly & you must just follow instructions below. (Experimental module, may not work properly!)
+1. CodeView & related abstract adapter to provide options & customization (see below).
2. For highlighting it uses CodeHighlighter, just highlights your code & returns formatted content. It based on [Google Prettify](https://github.com/google/code-prettify) and their Java implementation & [fork](https://github.com/google/code-prettify).
-3. CodeView & related abstract adapter to provide customization (see below).
+3. CodeClassifier is trying to define what language presented in code snippet. It built using [Naive Bayes classifier](https://en.wikipedia.org/wiki/Naive_Bayes_classifier) upon found open-source [implementation](https://github.com/ptnplanet/Java-Naive-Bayes-Classifier), which I rewrote in Kotlin. There is no need to work with this class directly & you must just follow instructions below. (Experimental module, may not work properly!)
## Download Add it in your root ```build.gradle``` at the end of repositories: @@ -28,7 +28,7 @@ allprojects { Add the dependency: ```groovy -compile 'com.github.softwee:codeview-android:1.1.2' +compile 'com.github.softwee:codeview-android:1.2.0' ``` ## Usage @@ -38,72 +38,87 @@ If you want to use code classifier to auto language recognizing just add to your CodeProcessor.init(this); ``` -Add view for your layout: +Having done ones on app start you can classify language for different snippets more faster, because algorithm needs time for training on sets for presented listings of languages which library has. + +Add view to your layout & bind as usual: ```xml ``` - -Use chaining syntax when build view: ```java CodeView codeView = (CodeView) findViewById(R.id.code_view); - -codeView.highlightCode("js") - .setColorTheme(ColorTheme.SOLARIZED_LIGHT.withBgContent(myColor)) - .setCodeContent(getString(R.string.listing_js)); ``` -And perform actions sequentially when view built: +So now you can set code using implicit form: ```java -codeView.setCodeContent(getString(R.string.listing_java)); -codeView.highlightCode("java"); +// auto language recognition +codeView.setCode(getString(R.string.listing_js)); ``` -You can use both forms for build & built view, but note: ```setCodeContent(String)``` is final step when you build your view, otherwise not. If you firstly highlight and then set code content, code will not be highlighted if view was not built yet. Instructions above helps you to avoid errors. View has state to handle this behavior. - -## Customizing -Use implicit form to code highlighting: +Or explicit (see available extensions below): ```java -codeView.highlightCode(); +// will work faster! +codeView.setCode(getString(R.string.listing_py), "py"); ``` -or eplixit (see available extensions below): + +## Customization +When you call ```setCode(...)``` view will prepared with default params if view was not initialized before. So if you want some customization, it can be done using options and/or adapter. + +### Initialization +You can initialize view with options: ```java -codeView.highlightCode("js"); // it will work fast! +codeView.setOptions(Options.Default.get(this) + .withLanguage("python") + .withCode(R.string.listing_py) + .withTheme(ColorTheme.MONOKAI)); ``` -Use default color theme: +Or using adapter (see Adapter or example for more details): ```java -codeView.setColorTheme(ColorTheme.SOLARIZED_LIGHT); +final CustomAdapter myAdapter = new CustomAdapter(this, getString(R.string.listing_md)); +codeView.setAdapter(myAdapter); ``` -or extend default: + +Note: Each CodeView has adapter and each adapter has options. When calling ```setOptions(...)``` or ```setAdapter(...)``` current adapter "flushed" with current options. If you want to save the state and just update options saving adapter or set adapter saving options you must call ```updateOptions(...)``` or ```updateAdapter(...)``` accordingly. + +### Options +Options helps to easily set necessary params, such as code & language, color theme, shortcut params (max lines, note), code line click listener. Some params are unnecessary. + +When view initialized (options or adapter are set) you can manipulate options in various ways: ```java -int myColor = ContextCompat.getColor(this, R.color.my_color); -codeView.setColorTheme(ColorTheme.MONOKAI.withBgContent(myColor)); +codeView.getOptions() + .withCode(R.string.listing_java) + .withLanguage("java") + .withTheme(ColorTheme.MONOKAI); ``` -or provide your own (don't forget to open PR with this stuff!) + +### Color theme +There are some default themes (see full list below): ```java -codeView.setColorTheme(new ColorThemeData(new SyntaxColors(...), ...)); +codeView.getOptions().setTheme(ColorTheme.SOLARIZED_LIGHT); ``` -Handle user clicks on code lines: +But you can build your own from existing one: ```java -codeView.setCodeListener(new OnCodeLineClickListener() { - @Override - public void onCodeLineClicked(int n, @NotNull String line) { - Log.i("ListingsActivity", "On " + (n + 1) + " line clicked"); - } -}); +ColorThemeData myTheme = ColorTheme.SOLARIZED_LIGHT.theme() + .withBgContent(android.R.color.black) + .withNoteColor(android.R.color.white); + +codeView.getOptions().setTheme(myTheme); ``` -Enable shadows to hide scrolled content: +Or create your own from scratch (don't forget to open PR with this stuff!): ```java -codeView.setShadowsEnabled(true); +ColorThemeData customTheme = new ColorThemeData(new SyntaxColors(...), ...); +codeView.getOptions().setTheme(customTheme); ``` -## Adapter customization -Sometimes you may want to add some content under line. You can create your own implementation as follows: +### Adapter +Sometimes you may want to take code lines under your control, and that's why you need Adapter. + +You can create your own implementation as follows: 1. Create your model to store data, for example some ```MyModel``` class.
2. Extend ```AbstractCodeAdapter``` typed by your model class.
@@ -169,14 +184,14 @@ C/C++/Objective-C (```"c"```, ```"cc"```, ```"cpp"```, ```"cxx"```, ```"cyc"```, Didn't found yours? Please, open issue to show your interest & I'll try to add this language in next releases. ## List of available themes -1. Default (simple light theme) -2. Solarized Light -3. Monokai +1. Default (simple light theme). +2. Solarized Light. +3. Monokai. ## Contribute 1. You can add your theme (see [ColorTheme](https://github.com/Softwee/codeview-android/blob/master/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt) class). Try to add some classic color themes or create your own if it looks cool. You can find many of them in different open-source text editors.
-2. If you are strong in a regex add missed language as shown [here](https://github.com/Softwee/codeview-android/blob/master/codeview/src/main/java/io/github/kbiakov/codeview/highlight/prettify/lang/LangScala.java). You can find existing regex for some language in different sources of js-libraries, etc, which plays the same role.
-3. Various adapters also welcome, customization is unlimited. +2. If you are strong in regex, add missed language as shown [here](https://github.com/Softwee/codeview-android/blob/master/codeview/src/main/java/io/github/kbiakov/codeview/highlight/prettify/lang/LangScala.java). You can find existing regex for some language in different sources of libraries, which plays the same role.
+3. Various adapters also welcome. ## Author ### [Kirill Biakov](https://github.com/kbiakov) diff --git a/codeview/build.gradle b/codeview/build.gradle index 4813467..79feaca 100644 --- a/codeview/build.gradle +++ b/codeview/build.gradle @@ -2,12 +2,12 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 24 - buildToolsVersion "24.0.2" + compileSdkVersion 25 + buildToolsVersion "25.0.1" defaultConfig { minSdkVersion 15 - targetSdkVersion 24 + targetSdkVersion 25 versionCode 1 versionName "1.0" } @@ -25,8 +25,8 @@ android { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - compile 'com.android.support:appcompat-v7:24.2.0' - compile 'com.android.support:recyclerview-v7:24.2.0' + compile 'com.android.support:appcompat-v7:25.0.1' + compile 'com.android.support:recyclerview-v7:25.0.1' } repositories { mavenCentral() diff --git a/codeview/src/main/assets/training-set/javascript/new 3.txt b/codeview/src/main/assets/training-set/javascript/new 3.txt index ae5ac2b..37bc1f0 100755 --- a/codeview/src/main/assets/training-set/javascript/new 3.txt +++ b/codeview/src/main/assets/training-set/javascript/new 3.txt @@ -67,32 +67,32 @@ var minExpectedLifetime = '20s'; * @api public */ -function UpServer (server, file, opts) { - if (this == global) return new UpServer(server, file, opts); +function UpServer (server, file, options) { + if (this == global) return new UpServer(server, file, options); Distributor.call(this, server); var self = this; - opts = opts || {}; + options = options || {}; this.file = file; - this.numWorkers = eq(opts.numWorkers || numWorkers, { cpus: cpus }); - this.workerTimeout = ms(null != opts.workerTimeout - ? opts.workerTimeout : workerTimeout); - this.requires = opts.requires || []; - this.assumeReady = opts.assumeReady === undefined ? true : !!opts.assumeReady; - this.keepAlive = opts.keepAlive || false; - this.minExpectedLifetime = ms(opts.minExpectedLifetime != null ? opts.minExpectedLifetime : minExpectedLifetime); - if (false !== opts.workerPingInterval) { - this.workerPingInterval = ms(opts.workerPingInterval || '1m'); + this.numWorkers = eq(options.numWorkers || numWorkers, { cpus: cpus }); + this.workerTimeout = ms(null != options.workerTimeout + ? options.workerTimeout : workerTimeout); + this.requires = options.requires || []; + this.assumeReady = options.assumeReady === undefined ? true : !!options.assumeReady; + this.keepAlive = options.keepAlive || false; + this.minExpectedLifetime = ms(options.minExpectedLifetime != null ? options.minExpectedLifetime : minExpectedLifetime); + if (false !== options.workerPingInterval) { + this.workerPingInterval = ms(options.workerPingInterval || '1m'); } this.workers = []; this.spawning = []; this.lastIndex = -1; - if (opts.title) { - this.title = opts.title; + if (options.title) { + this.title = options.title; process.title = this.title + ' master'; } @@ -290,7 +290,7 @@ function Worker (server) { this.server = server; this.readyState = 'spawning'; - var opts = JSON.stringify({ + var options = JSON.stringify({ file: server.file , requires: server.requires , assumeReady: server.assumeReady @@ -298,7 +298,7 @@ function Worker (server) { , title: server.title }); - this.proc = fork(__dirname + '/worker.js', [opts], { env: process.env }); + this.proc = fork(__dirname + '/worker.js', [options], { env: process.env }); this.proc.on('message', this.onMessage.bind(this)); this.proc.on('exit', this.onExit.bind(this)); this.pid = this.proc.pid; diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt b/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt index 0e69793..830b266 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt @@ -9,66 +9,50 @@ import android.widget.RelativeLayout import io.github.kbiakov.codeview.Thread.delayed import io.github.kbiakov.codeview.adapters.AbstractCodeAdapter import io.github.kbiakov.codeview.adapters.CodeWithNotesAdapter +import io.github.kbiakov.codeview.adapters.Options /** * @class CodeView * - * Presents your code content. - * - * Before view built or started to, as the first step, placeholder - * measures & prepare place for code view. Amount of view params is - * not big, view has mutable state & non-standard initialization behavior. - * That is why there is no usual & well-known Builder pattern implementation. - * - * To control interaction state, being & built, was selected tasks queue. - * If user has already built view his task performs immediately, otherwise - * it puts in queue to awaiting adapter creation & processing by build flow. - * This helps to avoid errors & solve the init tasks in more elegant way. + * View for showing code content with syntax highlighting. * * @author Kirill Biakov */ -open class CodeView : RelativeLayout { +class CodeView(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) { private val vShadowRight: View private val vShadowBottomLine: View private val vShadowBottomContent: View - /** - * Core view to draw code by lines. - */ private val vCodeList: RecyclerView - fun getRecyclerView(): RecyclerView { - return vCodeList - } - /** - * Default constructor. + * Primary constructor. */ - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - val defaultAlpha = 0.7531f - var animateOnStart = true - - val a = context.theme.obtainStyledAttributes(attrs, R.styleable.CodeView, 0, 0) - try { - animateOnStart = a.getBoolean(R.styleable.CodeView_animateOnStart, animateOnStart) - } finally { - a.recycle() - } - animateOnStart = animateOnStart && visibility == View.VISIBLE + init { + val isAnimateOnStart = visibility == VISIBLE && { ctx: Context, ats: AttributeSet -> + val a = ctx.theme.obtainStyledAttributes(ats, R.styleable.CodeView, 0, 0) + + try { + a.getBoolean(R.styleable.CodeView_animateOnStart, true) + } finally { + a.recycle() + } + }(context, attrs) - alpha = if (animateOnStart) 0f else defaultAlpha + alpha = if (isAnimateOnStart) 0f else Consts.ALPHA inflate(context, R.layout.layout_code_view, this) - if (animateOnStart) { - animate().setDuration(Utils.DELAY * 5) - .alpha(defaultAlpha) - } + if (isAnimateOnStart) + animate() + .setDuration(Consts.DELAY * 5) + .alpha(Consts.ALPHA) - vShadowRight = findViewById(R.id.v_shadow_right)//todo: shadow color customization - vShadowBottomLine = findViewById(R.id.v_shadow_bottom_line)//todo: shadow color customization - vShadowBottomContent = findViewById(R.id.v_shadow_bottom_content)//todo: shadow color customization + // TODO: add shadow color customization + vShadowRight = findViewById(R.id.v_shadow_right) + vShadowBottomLine = findViewById(R.id.v_shadow_bottom_line) + vShadowBottomContent = findViewById(R.id.v_shadow_bottom_content) vCodeList = findViewById(R.id.rv_code_content) as RecyclerView vCodeList.layoutManager = LinearLayoutManager(context) @@ -76,98 +60,138 @@ open class CodeView : RelativeLayout { } /** - * Initialize RecyclerView with adapter - * then start highlighting + * Highlight code with defined programming language. + * It holds the placeholder on view until code is not highlighted. */ - fun init(h: Highlighter, adapter: AbstractCodeAdapter<*>) { - if (h.code.isEmpty()) { - throw IllegalStateException("Please set code() before init/highlight") - } - - vCodeList.adapter = adapter + private fun highlight() { + getAdapter()?.highlight { - setupShadows(adapter.highlighter.shadows) + animate() + .setDuration(Consts.DELAY * 2) + .alpha(.1f) - highlight() + delayed { + animate().alpha(1f) + getAdapter()?.notifyDataSetChanged() + } + } } /** - * Initialize RecyclerView with adapter - * then start highlighting + * Border shadows will shown if full listing presented. + * It helps to see what part of code is scrolled & hidden. + * + * @param isShadows Is shadows needed */ - fun init(adapter: AbstractCodeAdapter<*>) { - init(adapter.highlighter, adapter) + private fun setupShadows(isShadows: Boolean) { + val visibility = if (isShadows) VISIBLE else GONE + + vShadowRight.visibility = visibility + vShadowBottomLine.visibility = visibility + vShadowBottomContent.visibility = visibility } + // - Initialization + /** - * Initialize RecyclerView with adapter - * then start highlighting + * Prepare view with default adapter & options. */ - fun init(h: Highlighter) { - init(h, CodeWithNotesAdapter(context, h)) - } + private fun prepare() = setAdapter(CodeWithNotesAdapter(context)) /** - * Highlight code by defined programming language. - * It holds the placeholder on the view until code is highlighted. + * Initialize with options. + * + * @param options Options */ - fun highlight() { - if (vCodeList.adapter == null) { - throw IllegalStateException("Please set adapter or use init(highlighter) before highlight()") - } - - getAdapter()?.highlight() { - animate().setDuration(Utils.DELAY * 2) - .alpha(.1f) + fun setOptions(options: Options) = setAdapter(CodeWithNotesAdapter(context, options)) - delayed { - animate().alpha(1f) - vCodeList.adapter?.notifyDataSetChanged() - } - } + /** + * Initialize with adapter. + * + * @param adapter Adapter + */ + fun setAdapter(adapter: AbstractCodeAdapter<*>) { + vCodeList.adapter = adapter + setupShadows(adapter.options.shadows) + highlight() } + // - Options + /** - * Remove code listener. + * View options accessor. */ - fun removeLineClickListener() { - getAdapter()?.highlighter?.lineClickListener = null + fun getOptions(): Options? = getAdapter()?.options + fun getOptionsOrDefault() = getOptions() ?: Options(context) + + /** + * Update options or initialize if needed. + * + * @param options Options + */ + fun updateOptions(options: Options) { + if (getAdapter() == null) + setOptions(options) + else + getAdapter()!!.options = options } - fun getAdapter() = vCodeList.adapter as? AbstractCodeAdapter<*> + // - Adapter /** - * Update code. + * Code adapter accessor. */ - fun update(code: String) { - getAdapter()?.updateCode(code) - } + fun getAdapter() = vCodeList.adapter as? AbstractCodeAdapter<*> /** - * Update code. + * Update adapter or initialize if needed. + * + * @param adapter Adapter */ - fun update(h: Highlighter) { - init(getAdapter()!!.highlighter.update(h)) + fun updateAdapter(adapter: AbstractCodeAdapter<*>) { + adapter.options = getOptionsOrDefault() + setAdapter(adapter) } - // - Setup actions + // - Set code /** - * Border shadows will shown if presented full code listing. - * It helps user to see what part of code are scrolled & hidden. + * Set code content. + * + * There are two ways before code will be highlighted: + * 1) view is not initialized (adapter or options are not set), + * prepare with default params & try to classify language + * 2) view initialized with some params, language: + * a) is set: used defined programming language + * b) not set: try to classify + * + * @param code Code content */ - private fun setupShadows(shadows: Boolean) { - val visibility = if (shadows) VISIBLE else GONE + fun setCode(code: String) { + getAdapter() ?: prepare() + getAdapter()!!.updateCode(code) + } - vShadowRight.visibility = visibility - vShadowBottomLine.visibility = visibility - vShadowBottomContent.visibility = visibility + /** + * Set code content. + * + * There are two ways before code will be highlighted: + * 1) view is not initialized, prepare with default params + * 2) view initialized with some params, set new language + * + * @param code Code content + * @param language Programming language + */ + fun setCode(code: String, language: String) { + val options = getOptionsOrDefault() + updateOptions(options.withLanguage(language)) + getAdapter()!!.updateCode(code) } } /** - * Provides listener to code line clicks. + * Provide listener to code line clicks. */ interface OnCodeLineClickListener { - fun onLineClicked(n: Int, line: String) + fun onCodeLineClicked(n: Int, line: String) } diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/Highlighter.kt b/codeview/src/main/java/io/github/kbiakov/codeview/Highlighter.kt deleted file mode 100644 index 7cd22a6..0000000 --- a/codeview/src/main/java/io/github/kbiakov/codeview/Highlighter.kt +++ /dev/null @@ -1,141 +0,0 @@ -package io.github.kbiakov.codeview - -import android.content.Context -import io.github.kbiakov.codeview.highlight.ColorTheme -import io.github.kbiakov.codeview.highlight.ColorThemeData - -data class Highlighter(val context: Context) { - - var theme: ColorThemeData = ColorTheme.DEFAULT.theme() - var code = "" - var language: String? = null - var codeResId = 0 - var shortcut = false - var maxLines = 0 - var shortcutNote: String? = null - var shortcutNoteResId = 0 - var lineClickListener: OnCodeLineClickListener? = null - var shadows = false - - // - updated - - var themeUpdated = false - var codeUpdated = false - var languageUpdated = false - var codeResIdUpdated = false - var shortcutUpdated = false - var maxLinesUpdated = false - var shortcutNoteUpdated = false - var shortcutNoteResIdUpdated = false - var lineClickListenerUpdated = false - var shadowsUpdated = true - - fun language(language: String): Highlighter { - this.language = language - languageUpdated = true - return this - } - - fun code(code: String): Highlighter { - this.code = code - codeUpdated = true - return this - } - - fun code(codeResId: Int): Highlighter { - this.code = context.getString(codeResId) - codeResIdUpdated = true - return this - } - - fun shortcut(shortcut: Boolean): Highlighter { - this.shortcut = shortcut - shortcutUpdated = true - return this - } - - fun maxLines(maxLines: Int): Highlighter { - this.maxLines = maxLines - maxLinesUpdated = true - return this - } - - fun shortcutNote(shortcutNote: String): Highlighter { - this.shortcutNote = shortcutNote - shortcutNoteUpdated = true - return this - } - - fun shortcutNote(shortcutNoteResId: Int): Highlighter { - this.shortcutNote = context.getString(shortcutNoteResId) - shortcutNoteResIdUpdated = true - return this - } - - fun theme(theme: ColorTheme): Highlighter { - return this.theme(theme.theme()) - } - - fun theme(theme: ColorThemeData): Highlighter { - this.theme = theme - themeUpdated = true - return this - } - - fun lineClickListener(listener: OnCodeLineClickListener): Highlighter { - lineClickListener = listener - lineClickListenerUpdated = true - return this - } - - fun shadows(shadows: Boolean = true): Highlighter { - this.shadows = shadows - shadowsUpdated = true - return this - } - - /** - * Highlight code finally. - */ - fun highlight(codeView: CodeView) { - codeView.init(this) - } - - /** - * Update highlighter. - */ - fun update(newSettings: Highlighter): Highlighter { - if (newSettings.themeUpdated) { - theme = newSettings.theme - } - if (newSettings.codeUpdated) { - code = newSettings.code - } - if (newSettings.languageUpdated) { - language = newSettings.language - } - if (newSettings.codeResIdUpdated) { - codeResId = newSettings.codeResId - } - if (newSettings.shortcutUpdated) { - shortcut = newSettings.shortcut - } - if (newSettings.maxLinesUpdated) { - maxLines = newSettings.maxLines - } - if (newSettings.shortcutNoteUpdated) { - shortcutNote = newSettings.shortcutNote - } - if (newSettings.shortcutNoteResIdUpdated) { - shortcutNoteResId = newSettings.shortcutNoteResId - } - if (newSettings.lineClickListenerUpdated) { - lineClickListener = newSettings.lineClickListener - } - if (newSettings.shadowsUpdated) { - shadows = newSettings.shadows - } - - return this - } -} \ No newline at end of file diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt b/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt index 4f60c6b..66b3283 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt @@ -10,10 +10,9 @@ import java.io.BufferedReader import java.io.InputStreamReader import java.util.concurrent.Executors -class Utils { - companion object { - val DELAY: Long = 250 - } +object Consts { + val ALPHA = 0.7F + val DELAY = 250L } /** @@ -81,7 +80,8 @@ object Thread { * @param body Operation body * @param delayMs Delay in m */ - fun delayed(delayMs: Long = Utils.DELAY, body: () -> Unit) = Handler().postDelayed(body, delayMs) + fun delayed(delayMs: Long = Consts.DELAY, body: () -> Unit) = + Handler().postDelayed(body, delayMs) // - Extensions for block manipulations diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/adapters/AbstractCodeAdapter.kt b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/AbstractCodeAdapter.kt index 72693e6..e6decf3 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/adapters/AbstractCodeAdapter.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/AbstractCodeAdapter.kt @@ -12,10 +12,7 @@ import io.github.kbiakov.codeview.Thread.async import io.github.kbiakov.codeview.Thread.ui import io.github.kbiakov.codeview.classifier.CodeClassifier import io.github.kbiakov.codeview.classifier.CodeProcessor -import io.github.kbiakov.codeview.highlight.CodeHighlighter -import io.github.kbiakov.codeview.highlight.ColorThemeData -import io.github.kbiakov.codeview.highlight.MonoFontCache -import io.github.kbiakov.codeview.highlight.color +import io.github.kbiakov.codeview.highlight.* import java.util.* /** @@ -28,80 +25,48 @@ import java.util.* abstract class AbstractCodeAdapter : RecyclerView.Adapter { protected val context: Context - - var highlighter: Highlighter - internal set - - protected var lines: List = ArrayList() //items + protected var lines: List = ArrayList() // items protected var droppedLines: List? = null + internal var options: Options + private var footerEntities: HashMap> = HashMap() - constructor(context: Context, h: Highlighter) { + constructor(context: Context) { this.context = context - this.highlighter = h - + this.options = Options(context) prepareCodeLines() } - /** - * Adapter constructor. - * - * @param content Context - * @param content Code content - * @param isShowFull Do you want to show all code content? - * @param maxLines Max lines to show (when limit is reached, rest is dropped) - * @param shortcutNote When rest lines is dropped, note is shown as last string - * @param listener Listener to code line clicks - */ - constructor(context: Context, - content: String, - theme: ColorThemeData, - isShowFull: Boolean = true, - maxLines: Int = MAX_SHORTCUT_LINES, - shortcutNote: String = context.getString(R.string.show_all), - listener: OnCodeLineClickListener? = null) { + constructor(context: Context, code: String) { this.context = context - - highlighter = Highlighter(context) - - highlighter.code = content - highlighter.maxLines = maxLines - highlighter.lineClickListener = listener - highlighter.theme = theme - highlighter.shortcut = !isShowFull - highlighter.shortcutNote = shortcutNote - + this.options = Options(context, code) prepareCodeLines() } - fun isShorted(): Boolean = droppedLines != null - - //todo: showAllNotes() + constructor(context: Context, options: Options) { + this.context = context + this.options = options + prepareCodeLines() + } /** - * Split code content by lines. If listing must not be shown full it shows - * only necessary lines & rest are dropped (and stores in named variable). + * Split code content by lines. If listing must not be shown full, it shows + * only necessary lines & the rest are dropped (and stores in named variable). */ internal fun prepareCodeLines() { - val allLines = extractLines(highlighter.code) - val isFullShowing = !highlighter.shortcut || allLines.size <= highlighter.maxLines // limit is not reached + val allLines = extractLines(options.code) + val isFullShowing = !options.shortcut || allLines.size <= options.maxLines // limit is not reached - if (isFullShowing) { + if (isFullShowing) lines = allLines - return - } // else - - val resultLines = ArrayList(allLines.subList(0, highlighter.maxLines)) + else { + val resultLines = ArrayList(allLines.subList(0, options.maxLines)) + resultLines.add(options.shortcutNote.toUpperCase()) + lines = resultLines - if (!isFullShowing) { - droppedLines = ArrayList(allLines.subList(highlighter.maxLines, allLines.lastIndex)) - if (highlighter.shortcutNote != null) { - resultLines.add(highlighter.shortcutNote!!.toUpperCase()) - } + droppedLines = ArrayList(allLines.subList(options.maxLines, allLines.lastIndex)) } - - lines = resultLines } // - Adapter interface @@ -109,8 +74,8 @@ abstract class AbstractCodeAdapter : RecyclerView.Adapter : RecyclerView.Adapter : RecyclerView.Adapter Unit) { + internal fun highlight(onReady: () -> Unit) { async() { - val classifiedLanguage = highlighter.language ?: classifyContent() - highlighting(classifiedLanguage, onReady) + val language = options.language ?: classifyContent() + highlighting(language, onReady) } } @@ -153,7 +117,7 @@ abstract class AbstractCodeAdapter : RecyclerView.Adapter : RecyclerView.Adapter : RecyclerView.Adapter Unit) { - //todo: !!!performance (1) highlight next 10 then repeat (1) - val code = CodeHighlighter.highlight(language, highlighter.code, highlighter.theme) + // TODO: highlight by 10 lines + val code = CodeHighlighter.highlight(language, options.code, options.theme) updateContent(code, onReady) } @@ -193,8 +157,9 @@ abstract class AbstractCodeAdapter : RecyclerView.Adapter Unit) { - highlighter.code = code + options.code = code prepareCodeLines() + ui { onUpdated() } @@ -207,12 +172,12 @@ abstract class AbstractCodeAdapter : RecyclerView.Adapter : RecyclerView.Adapter : RecyclerView.Adapter : RecyclerView.Adapter val footerView = createFooter(context, entity, isFirst) - holder.llLineFooter.addView(footerView) - isFirst = false } } @@ -302,9 +265,9 @@ abstract class AbstractCodeAdapter : RecyclerView.Adapter : RecyclerView.Adapter { - constructor(context: Context, h: Highlighter) : super(context, h) + constructor(context: Context) : super(context) - /** - * Default constructor. - */ - constructor(context: Context, content: String, theme: ColorThemeData) : super(context, content, theme) + constructor(context: Context, code: String) : super(context, code) + + constructor(context: Context, options: Options) : super(context, options) /** * Create footer view. diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/adapters/CodeWithNotesAdapter.kt b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/CodeWithNotesAdapter.kt index bd36c0f..dff3663 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/adapters/CodeWithNotesAdapter.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/CodeWithNotesAdapter.kt @@ -1,8 +1,6 @@ package io.github.kbiakov.codeview.adapters import android.content.Context -import io.github.kbiakov.codeview.Highlighter -import io.github.kbiakov.codeview.highlight.ColorThemeData import io.github.kbiakov.codeview.highlight.color import io.github.kbiakov.codeview.views.LineNoteView @@ -15,14 +13,11 @@ import io.github.kbiakov.codeview.views.LineNoteView */ open class CodeWithNotesAdapter : AbstractCodeAdapter { - constructor(context: Context, h: Highlighter) : super(context, h) + constructor(context: Context) : super(context) - /** - * Default constructor. - */ - constructor(context: Context, content: String, colorTheme: ColorThemeData) : super(context, content, colorTheme) + constructor(context: Context, code: String) : super(context, code) - //todo: inflateFooter(int layoutId) + constructor(context: Context, options: Options) : super(context, options) /** * Create footer view. @@ -34,6 +29,6 @@ open class CodeWithNotesAdapter : AbstractCodeAdapter { LineNoteView.create(context, text = entity, isFirst = isFirst, - bgColor = highlighter.theme.bgNum.color(), - textColor = highlighter.theme.noteColor.color()) + bgColor = options.theme.bgNum.color(), + textColor = options.theme.noteColor.color()) } diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt b/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt index 829842f..6269a41 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt @@ -33,9 +33,9 @@ object CodeHighlighter { val colorsMap = buildColorsMap(colorTheme) val highlighted = StringBuilder() - results.forEach { result -> - val color = colorsMap.getColor(result) - val content = parseContent(source, result) + results.forEach { + val color = colorsMap.getColor(it) + val content = parseContent(source, it) highlighted.append(content.withFontParams(color)) } @@ -139,6 +139,24 @@ enum class ColorTheme( bgNum = 0xF2F2F6, noteColor = 0x4C5D6E); + fun theme() = ColorThemeData( + syntaxColors, + numColor, + bgContent, + bgNum, + noteColor) +} + +/** + * Custom color theme. + */ +data class ColorThemeData( + val syntaxColors: SyntaxColors = SyntaxColors(), + val numColor: Int, + val bgContent: Int, + val bgNum: Int, + val noteColor: Int) { + /** * Decompose preset color theme to data. * Use this form for using from Kotlin. @@ -149,19 +167,7 @@ enum class ColorTheme( myBgContent: Int = bgContent, myBgNum: Int = bgNum, myNoteColor: Int = noteColor - ) = ColorThemeData( - mySyntaxColors, - myNumColor, - myBgContent, - myBgNum, - myNoteColor) - - fun theme() = ColorThemeData( - syntaxColors, - numColor, - bgContent, - bgNum, - noteColor) + ) = this /** * Decompose preset color theme to data. @@ -183,16 +189,6 @@ enum class ColorTheme( with(myNoteColor = myNoteColor) } -/** - * Custom color theme. - */ -data class ColorThemeData( - val syntaxColors: SyntaxColors = SyntaxColors(), - val numColor: Int, - val bgContent: Int, - val bgNum: Int, - val noteColor: Int) - /** * Colors for highlighting code units. */ diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/views/LineDiffView.kt b/codeview/src/main/java/io/github/kbiakov/codeview/views/LineDiffView.kt index d496c73..d1af231 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/views/LineDiffView.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/views/LineDiffView.kt @@ -48,7 +48,8 @@ class LineDiffView : RelativeLayout { diffView.setBackgroundColor(ContextCompat.getColor(context, if (model.isAddition) R.color.diff_add_background - else R.color.diff_del_background)) + else + R.color.diff_del_background)) return diffView } diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/views/LineNoteView.kt b/codeview/src/main/java/io/github/kbiakov/codeview/views/LineNoteView.kt index 9a23369..53c6880 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/views/LineNoteView.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/views/LineNoteView.kt @@ -37,7 +37,9 @@ class LineNoteView(context: Context?) : TextView(context) { val leftPadding = context.resources.getDimension( R.dimen.line_num_width).toInt() + dpToPx(context, 14) - noteView.setPadding(leftPadding, if (isFirst) dp8 else 0, dp8, dp8) + val topPadding = if (isFirst) dp8 else 0 + + noteView.setPadding(leftPadding, topPadding, dp8, dp8) return noteView } diff --git a/codeview/src/main/res/values/attrs.xml b/codeview/src/main/res/values/attrs.xml index ccf0da2..5bec0a9 100644 --- a/codeview/src/main/res/values/attrs.xml +++ b/codeview/src/main/res/values/attrs.xml @@ -3,5 +3,4 @@ - - \ No newline at end of file + diff --git a/example/src/main/java/io/github/kbiakov/codeviewexample/CustomAdapter.java b/example/src/main/java/io/github/kbiakov/codeviewexample/CustomAdapter.java index d7883a4..47e5e0a 100644 --- a/example/src/main/java/io/github/kbiakov/codeviewexample/CustomAdapter.java +++ b/example/src/main/java/io/github/kbiakov/codeviewexample/CustomAdapter.java @@ -8,12 +8,15 @@ import org.jetbrains.annotations.NotNull; import io.github.kbiakov.codeview.adapters.AbstractCodeAdapter; +import io.github.kbiakov.codeview.adapters.Options; import io.github.kbiakov.codeview.highlight.ColorTheme; public class CustomAdapter extends AbstractCodeAdapter { - public CustomAdapter(@NotNull Context context, @NotNull String content) { - super(context, content, ColorTheme.DEFAULT.theme(), true, 10, context.getString(R.string.show_all), null); + public CustomAdapter(@NotNull Context context, @NotNull String code) { + super(context, Options.Default.get(context) + .withCode(code) + .withTheme(ColorTheme.SOLARIZED_LIGHT)); } @NotNull diff --git a/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java b/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java index 0ffba70..1771daf 100644 --- a/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java +++ b/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java @@ -1,6 +1,5 @@ package io.github.kbiakov.codeviewexample; -import android.graphics.Color; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; @@ -9,10 +8,11 @@ import org.jetbrains.annotations.NotNull; import io.github.kbiakov.codeview.CodeView; -import io.github.kbiakov.codeview.Highlighter; import io.github.kbiakov.codeview.OnCodeLineClickListener; import io.github.kbiakov.codeview.adapters.CodeWithDiffsAdapter; +import io.github.kbiakov.codeview.adapters.Options; import io.github.kbiakov.codeview.highlight.ColorTheme; +import io.github.kbiakov.codeview.highlight.ColorThemeData; import io.github.kbiakov.codeview.views.DiffModel; public class ListingsActivity extends AppCompatActivity { @@ -25,79 +25,94 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { final CodeView codeView = (CodeView) findViewById(R.id.code_view); /** - * 1: default adapter with chaining build flow + * 1: set code content */ - // use chaining to build view with default adapter - new Highlighter(this) - .code(R.string.listing_js) - .language("js") - .theme(ColorTheme.DEFAULT.withBgContent(Color.WHITE)) - .highlight(codeView); + // auto language recognition + codeView.setCode(getString(R.string.listing_js)); + // specify language for code listing + codeView.setCode(getString(R.string.listing_py), "py"); /** - * 2: updating built view + * 2: working with options */ - // do not use chaining for built view - // (you can, but it should be performed sequentially) - final Highlighter h = new Highlighter(this) - .code(R.string.listing_java) - .language("java") - .theme(ColorTheme.SOLARIZED_LIGHT); + // you can change params as follows (unsafe, initialized view only) + codeView.getOptions() + .withCode(R.string.listing_java) + .withTheme(ColorTheme.MONOKAI); + + // short initialization with default params (can be expanded using with() methods) + codeView.setOptions(Options.Default.get(this) + .withLanguage("python") + .withCode(R.string.listing_py) + .withTheme(ColorTheme.MONOKAI)); + + // expanded form of initialization + codeView.setOptions(new Options( + this, // context + getString(R.string.listing_js), // code + "js", // language + ColorTheme.MONOKAI.theme(), // theme (data) + true, // shadows + true, // shortcut + getString(R.string.show_all), // shortcut note + 10, // max lines + new OnCodeLineClickListener() { // line click listener + @Override + public void onCodeLineClicked(int n, @NotNull String line) { + Log.i("ListingsActivity", "On " + (n + 1) + " line clicked"); + } + })); + + /** + * 3: color themes + */ + + codeView.getOptions().setTheme(ColorTheme.SOLARIZED_LIGHT); - h.setLineClickListener(new OnCodeLineClickListener() { - @Override - public void onLineClicked(int n, @NotNull String line) { - Log.i("ListingsActivity", "On " + (n + 1) + " line clicked"); - } - }); + // custom theme + ColorThemeData myTheme = ColorTheme.SOLARIZED_LIGHT.theme() + .withBgContent(android.R.color.black) + .withNoteColor(android.R.color.white); - codeView.init(h); + codeView.getOptions().setTheme(myTheme); /** - * 3: custom adapter with footer views + * 4: custom adapter with footer views */ - final CustomAdapter adapter = new CustomAdapter(this, getString(R.string.listing_md)); - codeView.init(adapter); - - codeView.update(new Highlighter(this) - .theme(ColorTheme.MONOKAI) - .lineClickListener(new OnCodeLineClickListener() { + final CustomAdapter myAdapter = new CustomAdapter(this, getString(R.string.listing_md)); + codeView.setAdapter(myAdapter); + codeView.getOptions() + .withLanguage("md") + .addCodeLineClickListener(new OnCodeLineClickListener() { @Override - public void onLineClicked(int n, @NotNull String line) { - adapter.addFooterEntity(n, new CustomAdapter.CustomModel("Line " + (n + 1), line)); + public void onCodeLineClicked(int n, @NotNull String line) { + myAdapter.addFooterEntity(n, new CustomAdapter.CustomModel("Line " + (n + 1), line)); } - }) - .language("md")); + }); /** - * 4: diff adapter with footer views + * 5: diff adapter with footer views */ - final CodeWithDiffsAdapter diffsAdapter = new CodeWithDiffsAdapter(ListingsActivity.this, - getString(R.string.listing_py), ColorTheme.SOLARIZED_LIGHT.theme()); - - diffsAdapter.getHighlighter().language("python"); - - codeView.removeLineClickListener(); - - codeView.init(diffsAdapter); + final CodeWithDiffsAdapter diffsAdapter = new CodeWithDiffsAdapter(this); + codeView.getOptions() + .withLanguage("python") + .setCode(getString(R.string.listing_py)); + codeView.updateAdapter(diffsAdapter); diffsAdapter.addFooterEntity(16, new DiffModel(getString(R.string.py_addition_16), true)); diffsAdapter.addFooterEntity(11, new DiffModel(getString(R.string.py_deletion_11), false)); /** - * 5: shortcut adapter with footer views + * 6: shortcut adapter with footer views */ - new Highlighter(this).code(R.string.listing_py) - .shortcut(true) - .language("python") - .maxLines(3) - .shortcutNote("Show All") - .highlight(codeView); + + codeView.getOptions() + .shortcut(10, "Show all"); } }