diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 607b9e9330ad5baa38da222d170e584caae17001..50204202560529ea3bf30236e6b6448d782f1c17 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -111,7 +111,7 @@ dependencies {
   }
 
   // App Arch Persistence
-  withVersion("1.1.0-alpha3") {
+  withVersion("1.1.0-beta1") {
     implementation("android.arch.persistence.room", "runtime", version)
     implementation("android.arch.persistence.room", "rxjava2", version)
     kapt("android.arch.persistence.room", "compiler", version)
@@ -119,7 +119,7 @@ dependencies {
   }
 
   // App Arch Paging
-  implementation("android.arch.paging", "runtime", "1.0.0-alpha6") {
+  implementation("android.arch.paging", "runtime", "1.0.0-alpha7") {
     exclude(group = "junit", module = "junit")
   }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
index f5ea01c145ee26071a87ebe3d59c62b638907f74..6cd6dab9ab9143a16ab07ad75fc4c8ca780b8870 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
@@ -77,8 +77,7 @@ class QuasseldroidNG : Application() {
             } else {
               it
             }
-          }
-          .let {
+          }.let {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
               it.detectUnbufferedIo()
             } else {
@@ -100,8 +99,7 @@ class QuasseldroidNG : Application() {
             } else {
               it
             }
-          }
-          .let {
+          }.let {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
               it.detectContentUriWithoutPermission()
             } else {
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
index 8d7daca1d983c26728fc99662b396d28ce8e60b3..ca926a7d040891cd7071e06ddf0098eda05694ff 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
@@ -7,7 +7,6 @@ import android.arch.persistence.room.*
 import android.arch.persistence.room.migration.Migration
 import android.content.Context
 import android.support.annotation.IntRange
-import android.support.v7.recyclerview.extensions.DiffCallback
 import de.kuschku.libquassel.protocol.Message_Flag
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase.DatabaseMessage
@@ -46,14 +45,6 @@ abstract class QuasselDatabase : RoomDatabase() {
         flag
       )}, bufferId=$bufferId, sender='$sender', senderPrefixes='$senderPrefixes', content='$content')"
     }
-
-    object MessageDiffCallback : DiffCallback<DatabaseMessage>() {
-      override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage,
-                                      newItem: QuasselDatabase.DatabaseMessage) = oldItem == newItem
-
-      override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage,
-                                   newItem: QuasselDatabase.DatabaseMessage) = oldItem.messageId == newItem.messageId
-    }
   }
 
   @Dao
@@ -154,8 +145,7 @@ abstract class QuasselDatabase : RoomDatabase() {
                     "CREATE TABLE filtered(bufferId INTEGER, accountId INTEGER, filtered INTEGER, PRIMARY KEY(accountId, bufferId));"
                   )
                 }
-              }
-            ).build()
+              }).build()
           }
         }
       }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
index d270cc5c8091707720b01fcfaeecfb8af198f8cb..fee16389dbc7ffe988ac99cdd05026ccf5159acd 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
@@ -110,7 +110,8 @@ class QuasselService : LifecycleService(),
       }
       ConnectionState.INIT         -> {
         handle.builder.setContentTitle(getString(R.string.label_status_init))
-        handle.builder.setProgress(max, progress, false)
+        // Show indeterminate when no progress has been made yet
+        handle.builder.setProgress(max, progress, progress == 0 || max == 0)
       }
       ConnectionState.CONNECTED    -> {
         handle.builder.setContentTitle(getString(R.string.label_status_connected))
@@ -265,8 +266,7 @@ class QuasselService : LifecycleService(),
       .observe(
         this, Observer {
         sessionManager.reconnect(true)
-      }
-      )
+      })
 
     sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) {
       registerOnSharedPreferenceChangeListener(this@QuasselService)
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/settings/BacklogSettings.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/settings/BacklogSettings.kt
index c15f4e463b683e5a69fdfbe222b5bbfb19a724fc..0287243199a02bba066102d474e800eb44ce95b8 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/settings/BacklogSettings.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/settings/BacklogSettings.kt
@@ -1,7 +1,7 @@
 package de.kuschku.quasseldroid_ng.settings
 
 data class BacklogSettings(
-  val dynamicAmount: Int = 20
+  val dynamicAmount: Int = 150
 ) {
   companion object {
     val DEFAULT = BacklogSettings()
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/settings/Settings.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/settings/Settings.kt
index 175afcd6729f78a7c9572fc374c2849629c81435..24de3eb0079b2791bb07e03954855a67cf8d7f3f 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/settings/Settings.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/settings/Settings.kt
@@ -70,7 +70,7 @@ object Settings {
   fun backlog(context: Context) = context.sharedPreferences {
     BacklogSettings(
       dynamicAmount = getString(
-        context.getString(R.string.preference_dynamic_fetch_key),
+        context.getString(R.string.preference_page_size_key),
         BacklogSettings.DEFAULT.dynamicAmount.toString()
       ).toIntOrNull() ?: BacklogSettings.DEFAULT.dynamicAmount
     )
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
index 17c4644fa62e0c22d422a233054ed75528bd054a..4627d3e556189ec66359032deea8efd3cda42be5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
@@ -9,17 +9,16 @@ import android.content.SharedPreferences
 import android.os.Build
 import android.os.Bundle
 import android.os.PersistableBundle
-import android.support.v4.graphics.drawable.DrawableCompat
 import android.support.v4.widget.DrawerLayout
 import android.support.v7.app.ActionBarDrawerToggle
-import android.support.v7.widget.*
-import android.text.Editable
-import android.text.InputType
-import android.text.TextWatcher
-import android.view.*
-import android.view.inputmethod.EditorInfo
-import android.widget.EditText
-import android.widget.ImageButton
+import android.support.v7.widget.DefaultItemAnimator
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.support.v7.widget.Toolbar
+import android.view.Gravity
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
 import butterknife.BindView
 import butterknife.ButterKnife
 import com.afollestad.materialdialogs.MaterialDialog
@@ -33,29 +32,29 @@ import de.kuschku.libquassel.util.or
 import de.kuschku.quasseldroid_ng.Keys
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
-import de.kuschku.quasseldroid_ng.settings.AppearanceSettings
 import de.kuschku.quasseldroid_ng.settings.BacklogSettings
 import de.kuschku.quasseldroid_ng.settings.Settings
+import de.kuschku.quasseldroid_ng.ui.chat.input.AutoCompleteAdapter
+import de.kuschku.quasseldroid_ng.ui.chat.input.Editor
+import de.kuschku.quasseldroid_ng.ui.chat.input.MessageHistoryAdapter
 import de.kuschku.quasseldroid_ng.ui.settings.SettingsActivity
 import de.kuschku.quasseldroid_ng.ui.setup.accounts.AccountSelectionActivity
 import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel
 import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
-import de.kuschku.quasseldroid_ng.util.helper.*
+import de.kuschku.quasseldroid_ng.util.helper.editApply
+import de.kuschku.quasseldroid_ng.util.helper.invoke
+import de.kuschku.quasseldroid_ng.util.helper.let
+import de.kuschku.quasseldroid_ng.util.helper.sharedPreferences
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity
 import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar
-import io.reactivex.subjects.BehaviorSubject
 
-class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
-                     ActionMenuView.OnMenuItemClickListener {
+class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
   @BindView(R.id.drawer_layout)
   lateinit var drawerLayout: DrawerLayout
 
   @BindView(R.id.toolbar)
   lateinit var toolbar: Toolbar
 
-  @BindView(R.id.formatting_menu)
-  lateinit var formattingMenu: ActionMenuView
-
   @BindView(R.id.progress_bar)
   lateinit var progressBar: MaterialContentLoadingProgressBar
 
@@ -65,18 +64,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
   @BindView(R.id.history_panel)
   lateinit var historyPanel: SlidingUpPanelLayout
 
-  @BindView(R.id.send)
-  lateinit var send: ImageButton
-
-  @BindView(R.id.chatline)
-  lateinit var chatline: EditText
-
-  @BindView(R.id.autocomplete_list)
-  lateinit var autocompleteList: RecyclerView
-
-  @BindView(R.id.autocomplete_list2)
-  lateinit var autocompleteList2: RecyclerView
-
   @BindView(R.id.msg_history)
   lateinit var msgHistory: RecyclerView
 
@@ -90,7 +77,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
   private lateinit var backlogSettings: BacklogSettings
 
-  private lateinit var inputEditor: InputEditor
+  private lateinit var editor: Editor
 
   private val panelSlideListener: SlidingUpPanelLayout.PanelSlideListener = object :
     SlidingUpPanelLayout.PanelSlideListener {
@@ -99,57 +86,16 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     override fun onPanelStateChanged(panel: View?,
                                      previousState: SlidingUpPanelLayout.PanelState?,
                                      newState: SlidingUpPanelLayout.PanelState?) {
-      val selectionStart = chatline.selectionStart
-      val selectionEnd = chatline.selectionEnd
-
-      when (newState) {
-        SlidingUpPanelLayout.PanelState.COLLAPSED ->
-          chatline.inputType = chatline.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE.inv()
-        else                                      ->
-          chatline.inputType = chatline.inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
-      }
-
-      chatline.setSelection(selectionStart, selectionEnd)
+      editor.setMultiLine(newState == SlidingUpPanelLayout.PanelState.COLLAPSED)
     }
   }
 
-  private val lastWord = BehaviorSubject.createDefault(Pair("", IntRange.EMPTY))
-  private val textWatcher = object : TextWatcher {
-    override fun afterTextChanged(s: Editable?) {
-      val previous = autocompletionState
-      val next = if (previous != null && s != null) {
-        val suffix = if (previous.range.start == 0) ": " else " "
-        val end = Math.min(
-          s.length, previous.range.start + previous.completion.name.length + suffix.length
-        )
-        val sequence = s.substring(previous.range.start, end)
-        if (sequence == previous.completion.name + suffix) {
-          previous.originalWord to (previous.range.start until end)
-        } else {
-          autocompletionState = null
-          s.lastWordIndices(chatline.selectionStart, onlyBeforeCursor = true)?.let { indices ->
-            s.substring(indices) to indices
-          }
-        }
-      } else {
-        s?.lastWordIndices(chatline.selectionStart, onlyBeforeCursor = true)?.let { indices ->
-          s.substring(indices) to indices
-        }
-      }
-
-      lastWord.onNext(next ?: Pair("", IntRange.EMPTY))
-    }
-
-    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
-    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
-  }
-
   override fun onNewIntent(intent: Intent?) {
     super.onNewIntent(intent)
     if (intent != null) {
       when {
         intent.type == "text/plain" -> {
-          inputEditor.share(intent.getStringExtra(Intent.EXTRA_TEXT))
+          editor.formatHandler.replace(intent.getStringExtra(Intent.EXTRA_TEXT))
         }
       }
     }
@@ -163,90 +109,75 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
     viewModel = ViewModelProviders.of(this)[QuasselViewModel::class.java]
     viewModel.setBackend(this.backend)
-    viewModel.lastWord.value = lastWord
     backlogSettings = Settings.backlog(this)
 
-    inputEditor = InputEditor(chatline)
-    menuInflater.inflate(inputEditor.menu, formattingMenu.menu)
-    menuInflater.inflate(R.menu.input_panel, formattingMenu.menu)
-    formattingMenu.setOnMenuItemClickListener(this)
-
-    formattingMenu.context.theme.styledAttributes(R.attr.colorControlNormal) {
-      val color = getColor(0, 0)
-
-      for (item in (0 until formattingMenu.menu.size()).map { formattingMenu.menu.getItem(it) }) {
-        val drawable = item.icon.mutate()
-        DrawableCompat.setTint(drawable, color)
-        item.icon = drawable
+    editor = Editor(
+      this,
+      viewModel.autoCompleteData,
+      viewModel.lastWord,
+      findViewById(R.id.chatline),
+      findViewById(R.id.send),
+      listOf(
+        findViewById(R.id.autocomplete_list),
+        findViewById(R.id.autocomplete_list_expanded)
+      ),
+      findViewById(R.id.formatting_menu),
+      findViewById(R.id.formatting_toolbar),
+      { lines ->
+        viewModel.session { session ->
+          viewModel.getBuffer().let { bufferId ->
+            session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
+              val output = mutableListOf<IAliasManager.Command>()
+              for ((stripped, formatted) in lines) {
+                viewModel.addRecentlySentMessage(stripped)
+                session.aliasManager?.processInput(bufferInfo, formatted, output)
+              }
+              for (command in output) {
+                session.rpcHandler?.sendInput(command.buffer, command.message)
+              }
+            }
+          }
+        }
+      },
+      { expanded ->
+        historyPanel.panelState = if (expanded)
+          SlidingUpPanelLayout.PanelState.EXPANDED
+        else
+          SlidingUpPanelLayout.PanelState.COLLAPSED
       }
-    }
+    )
 
     msgHistory.itemAnimator = DefaultItemAnimator()
     msgHistory.layoutManager = LinearLayoutManager(this)
-    msgHistory.adapter = MessageHistoryAdapter(
-      this,
-      viewModel.recentlySentMessages,
-      handler::post,
-      ::runOnUiThread,
-      { text ->
-        chatline.setText(text)
-        chatline.setSelection(chatline.length())
-        historyPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED
-      }
-    )
+    val messageHistoryAdapter = MessageHistoryAdapter { text ->
+      editor.formatHandler.replace(text)
+      historyPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED
+    }
+    msgHistory.adapter = messageHistoryAdapter
+    viewModel.recentlySentMessages.observe(this, Observer(messageHistoryAdapter::submitList))
 
     database = QuasselDatabase.Creator.init(application)
 
     setSupportActionBar(toolbar)
 
-    send.setOnClickListener {
-      send()
-    }
-
-    chatline.imeOptions = when (appearanceSettings.inputEnter) {
-      AppearanceSettings.InputEnterMode.EMOJI -> listOf(
-        EditorInfo.IME_ACTION_NONE,
-        EditorInfo.IME_FLAG_NO_EXTRACT_UI
-      )
-      AppearanceSettings.InputEnterMode.SEND  -> listOf(
-        EditorInfo.IME_ACTION_SEND,
-        EditorInfo.IME_FLAG_NO_EXTRACT_UI
-      )
-    }.fold(0, Int::or)
-
-    chatline.setOnKeyListener { _, keyCode, event ->
-      when (keyCode) {
-        KeyEvent.KEYCODE_ENTER,
-        KeyEvent.KEYCODE_NUMPAD_ENTER -> if (event.hasNoModifiers()) {
-          send()
-          true
-        } else {
-          false
-        }
-        KeyEvent.KEYCODE_TAB          -> {
-          autoComplete(event.isShiftPressed)
-          true
-        }
-        else                          -> false
-      }
-    }
-
     viewModel.getBuffer().observe(
       this, Observer {
       if (it != null && drawerLayout.isDrawerOpen(Gravity.START)) {
         drawerLayout.closeDrawer(Gravity.START, true)
       }
-    }
-    )
+    })
 
-    supportActionBar?.setDisplayHomeAsUpEnabled(true)
-    drawerToggle = ActionBarDrawerToggle(
-      this,
-      drawerLayout,
-      R.string.label_open,
-      R.string.label_close
-    )
-    drawerToggle.syncState()
+    // Don’t show a drawer toggle if in tablet landscape mode
+    if (resources.getBoolean(R.bool.buffer_drawer_exists)) {
+      supportActionBar?.setDisplayHomeAsUpEnabled(true)
+      drawerToggle = ActionBarDrawerToggle(
+        this,
+        drawerLayout,
+        R.string.label_open,
+        R.string.label_close
+      )
+      drawerToggle.syncState()
+    }
 
     viewModel.connectionProgress.observe(this, Observer { it ->
       val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0)
@@ -255,7 +186,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
           progressBar.hide()
         }
         ConnectionState.INIT                                    -> {
-          progressBar.isIndeterminate = false
+          // Show indeterminate when no progress has been made yet
+          progressBar.isIndeterminate = progress == 0 || max == 0
           progressBar.progress = progress
           progressBar.max = max
         }
@@ -265,27 +197,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       }
     })
 
-    val autocompleteAdapter = AutoCompleteAdapter(
-      this,
-      viewModel.autoCompleteData,
-      handler::post,
-      ::runOnUiThread,
-      // This is still broken when mixing tab complete and UI auto complete
-      inputEditor::autoComplete
-    )
-
-    if (appearanceSettings.showAutocomplete) {
-      autocompleteList.layoutManager = LinearLayoutManager(this)
-      autocompleteList.itemAnimator = DefaultItemAnimator()
-      autocompleteList.adapter = autocompleteAdapter
-
-      autocompleteList2.layoutManager = LinearLayoutManager(this)
-      autocompleteList2.itemAnimator = DefaultItemAnimator()
-      autocompleteList2.adapter = autocompleteAdapter
-    }
-
-    chatline.addTextChangedListener(textWatcher)
-
     editorPanel.addPanelSlideListener(panelSlideListener)
     editorPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED
   }
@@ -297,78 +208,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     val completion: AutoCompleteAdapter.AutoCompleteItem
   )
 
-  private var autocompletionState: AutoCompletionState? = null
-  private fun autoComplete(reverse: Boolean = false) {
-    val originalWord = lastWord.value
-
-    val previous = autocompletionState
-    if (!originalWord.second.isEmpty()) {
-      val autoCompletedWords = viewModel.autoCompleteData.value?.second.orEmpty()
-      if (previous != null && lastWord.value.first == previous.originalWord && lastWord.value.second.start == previous.range.start) {
-        val previousIndex = autoCompletedWords.indexOf(previous.completion)
-        val autoCompletedWord = if (previousIndex != -1) {
-          val change = if (reverse) -1 else +1
-          val newIndex = (previousIndex + change + autoCompletedWords.size) % autoCompletedWords.size
-
-          autoCompletedWords[newIndex]
-        } else {
-          autoCompletedWords.firstOrNull()
-        }
-        if (autoCompletedWord != null) {
-          val newState = AutoCompletionState(
-            previous.originalWord,
-            originalWord.second,
-            previous.completion,
-            autoCompletedWord
-          )
-          autocompletionState = newState
-          inputEditor.autoComplete(newState)
-        } else {
-          autocompletionState = null
-        }
-      } else {
-        val autoCompletedWord = autoCompletedWords.firstOrNull()
-        if (autoCompletedWord != null) {
-          val newState = AutoCompletionState(
-            originalWord.first,
-            originalWord.second,
-            null,
-            autoCompletedWord
-          )
-          autocompletionState = newState
-          inputEditor.autoComplete(newState)
-        } else {
-          autocompletionState = null
-        }
-      }
-    }
-  }
-
-  private fun send() {
-    val text = chatline.text
-    if (text.isNotBlank()) {
-      viewModel.session { session ->
-        viewModel.getBuffer().let { bufferId ->
-          session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
-            val output = mutableListOf<IAliasManager.Command>()
-            for (line in text.lineSequence()) {
-              viewModel.addRecentlySentMessage(line)
-              session.aliasManager?.processInput(
-                bufferInfo,
-                inputEditor.formattedString,
-                output
-              )
-            }
-            for (command in output) {
-              session.rpcHandler?.sendInput(command.buffer, command.message)
-            }
-          }
-        }
-      }
-    }
-    chatline.setText("")
-  }
-
   override fun onSaveInstanceState(outState: Bundle?) {
     super.onSaveInstanceState(outState)
     outState?.putInt("OPEN_BUFFER", viewModel.getBuffer().value ?: -1)
@@ -492,14 +331,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     else -> super.onOptionsItemSelected(item)
   }
 
-  override fun onMenuItemClick(item: MenuItem?) = when (item?.itemId) {
-    R.id.input_history -> {
-      historyPanel.panelState = SlidingUpPanelLayout.PanelState.EXPANDED
-      true
-    }
-    else               -> inputEditor.onMenuItemClick(item)
-  }
-
   override fun onDestroy() {
     handler.onDestroy()
     super.onDestroy()
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageHistoryAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageHistoryAdapter.kt
deleted file mode 100644
index c88ed9581b1761009e0a9502f04f8c0f83541a70..0000000000000000000000000000000000000000
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageHistoryAdapter.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package de.kuschku.quasseldroid_ng.ui.chat
-
-import android.arch.lifecycle.LifecycleOwner
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.Observer
-import android.support.v7.util.DiffUtil
-import android.support.v7.widget.RecyclerView
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import butterknife.BindView
-import butterknife.ButterKnife
-import de.kuschku.quasseldroid_ng.R
-
-class MessageHistoryAdapter(
-  lifecycleOwner: LifecycleOwner,
-  liveData: LiveData<List<CharSequence>?>,
-  runInBackground: (() -> Unit) -> Any,
-  runOnUiThread: (Runnable) -> Any,
-  private val clickListener: ((CharSequence) -> Unit)? = null
-) : RecyclerView.Adapter<MessageHistoryAdapter.MessageViewHolder>() {
-  var data = mutableListOf<CharSequence>()
-
-  init {
-    liveData.observe(lifecycleOwner, Observer { it: List<CharSequence>? ->
-      runInBackground {
-        val list = it ?: emptyList()
-        val old: List<CharSequence> = data
-        val new: List<CharSequence> = list
-        val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
-          override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
-            old[oldItemPosition] == new[newItemPosition]
-
-          override fun getOldListSize() = old.size
-          override fun getNewListSize() = new.size
-          override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
-            old[oldItemPosition] == new[newItemPosition]
-        }, true)
-        runOnUiThread(Runnable {
-          data.clear()
-          data.addAll(new)
-          result.dispatchUpdatesTo(this@MessageHistoryAdapter)
-        })
-      }
-    })
-  }
-
-  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MessageViewHolder(
-    LayoutInflater.from(parent.context).inflate(R.layout.widget_history_message, parent, false),
-    clickListener = clickListener
-  )
-
-  override fun onBindViewHolder(holder: MessageViewHolder, position: Int) =
-    holder.bind(data[position])
-
-  override fun getItemCount() = data.size
-
-  class MessageViewHolder(
-    itemView: View,
-    private val clickListener: ((CharSequence) -> Unit)? = null
-  ) : RecyclerView.ViewHolder(itemView) {
-    @BindView(R.id.content)
-    lateinit var content: TextView
-
-    var value: CharSequence? = null
-
-    init {
-      ButterKnife.bind(this, itemView)
-      itemView.setOnClickListener {
-        val value = value
-        if (value != null)
-          clickListener?.invoke(value)
-      }
-    }
-
-    fun bind(data: CharSequence) {
-      value = data
-
-      content.text = data
-    }
-  }
-}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListAdapter.kt
deleted file mode 100644
index ce4f8b57e1ddcfb842acf1bce1b71a04b5a1f642..0000000000000000000000000000000000000000
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListAdapter.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-package de.kuschku.quasseldroid_ng.ui.chat
-
-import android.arch.lifecycle.LifecycleOwner
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.Observer
-import android.support.v7.util.DiffUtil
-import android.support.v7.widget.RecyclerView
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import butterknife.BindView
-import butterknife.ButterKnife
-import de.kuschku.libquassel.util.irc.IrcCaseMappers
-import de.kuschku.quasseldroid_ng.R
-import de.kuschku.quasseldroid_ng.util.helper.visibleIf
-
-class NickListAdapter(
-  lifecycleOwner: LifecycleOwner,
-  liveData: LiveData<List<IrcUserItem>?>,
-  runInBackground: (() -> Unit) -> Any,
-  runOnUiThread: (Runnable) -> Any,
-  private val clickListener: ((String) -> Unit)? = null
-) : RecyclerView.Adapter<NickListAdapter.NickViewHolder>() {
-  var data = mutableListOf<IrcUserItem>()
-
-  init {
-    liveData.observe(
-      lifecycleOwner, Observer { it: List<IrcUserItem>? ->
-      runInBackground {
-        val list = it ?: emptyList()
-        val old: List<IrcUserItem> = data
-        val new: List<IrcUserItem> = list
-          .sortedBy { IrcCaseMappers[it.networkCasemapping].toLowerCase(it.nick) }
-          .sortedBy { it.lowestMode }
-        val result = DiffUtil.calculateDiff(
-          object : DiffUtil.Callback() {
-            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int)
-              = old[oldItemPosition].nick == new[newItemPosition].nick
-
-            override fun getOldListSize() = old.size
-            override fun getNewListSize() = new.size
-            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int)
-              = old[oldItemPosition] == new[newItemPosition]
-          }, true
-        )
-        runOnUiThread(
-          Runnable {
-            data.clear()
-            data.addAll(new)
-            result.dispatchUpdatesTo(this@NickListAdapter)
-          }
-        )
-      }
-    }
-    )
-  }
-
-  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = NickViewHolder(
-    LayoutInflater.from(parent.context).inflate(
-      when (viewType) {
-        VIEWTYPE_AWAY -> R.layout.widget_nick_away
-        else          -> R.layout.widget_nick
-      }, parent, false
-    ),
-    clickListener = clickListener
-  )
-
-  override fun onBindViewHolder(holder: NickViewHolder, position: Int)
-    = holder.bind(data[position])
-
-  override fun getItemCount() = data.size
-
-  override fun getItemViewType(position: Int) = if (data[position].away) {
-    VIEWTYPE_AWAY
-  } else {
-    VIEWTYPE_ACTIVE
-  }
-
-  data class IrcUserItem(
-    val nick: String,
-    val modes: String,
-    val lowestMode: Int,
-    val realname: CharSequence,
-    val away: Boolean,
-    val networkCasemapping: String
-  )
-
-  class NickViewHolder(
-    itemView: View,
-    private val clickListener: ((String) -> Unit)? = null
-  ) : RecyclerView.ViewHolder(itemView) {
-    @BindView(R.id.modesContainer)
-    lateinit var modesContainer: View
-
-    @BindView(R.id.modes)
-    lateinit var modes: TextView
-
-    @BindView(R.id.nick)
-    lateinit var nick: TextView
-
-    @BindView(R.id.realname)
-    lateinit var realname: TextView
-
-    var user: String? = null
-
-    init {
-      ButterKnife.bind(this, itemView)
-      itemView.setOnClickListener {
-        val nick = user
-        if (nick != null)
-          clickListener?.invoke(nick)
-      }
-    }
-
-    fun bind(data: IrcUserItem) {
-      user = data.nick
-
-      nick.text = data.nick
-      modes.text = data.modes
-      realname.text = data.realname
-
-      modes.visibleIf(data.modes.isNotBlank())
-    }
-  }
-
-  companion object {
-    val VIEWTYPE_ACTIVE = 0
-    val VIEWTYPE_AWAY = 1
-  }
-}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt
index 207c933b878bd1162a33f0ba2969bae102431539..7c8b69f1c9e36817db540375e36f0d2a066f3d11 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt
@@ -93,8 +93,7 @@ class ToolbarFragment : ServiceBoundFragment() {
           }
         }
       }
-    }
-    )
+    })
 
     return view
   }
@@ -109,5 +108,4 @@ class ToolbarFragment : ServiceBoundFragment() {
     val network: INetwork.NetworkInfo? = null,
     val description: String? = null
   )
-
 }
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt
index 6a437ebc1cfb7f831fb874f1827783241cad565a..f5cbb490b395d697103dbc7e00187f3ebf2ddfda 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt
@@ -70,27 +70,27 @@ class BufferListAdapter(
         }.sortedBy { props ->
           props.network.networkName
         }.map { props ->
-            BufferListItem(
-              props,
-              BufferState(
-                networkExpanded = !collapsedNetworks.contains(props.network.networkId),
-                selected = selected == props.info.bufferId
-              )
+          BufferListItem(
+            props,
+            BufferState(
+              networkExpanded = !collapsedNetworks.contains(props.network.networkId),
+              selected = selected == props.info.bufferId
             )
+          )
         }.filter { (props, state) ->
           props.info.type.hasFlag(BufferInfo.Type.StatusBuffer) || state.networkExpanded
         }
 
         val result = DiffUtil.calculateDiff(
           object : DiffUtil.Callback() {
-            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int)
-              = old[oldItemPosition].props.info.bufferId == new[newItemPosition].props.info.bufferId
+            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
+              old[oldItemPosition].props.info.bufferId == new[newItemPosition].props.info.bufferId
 
             override fun getOldListSize() = old.size
             override fun getNewListSize() = new.size
 
-            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int)
-              = old[oldItemPosition] == new[newItemPosition]
+            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
+              old[oldItemPosition] == new[newItemPosition]
           }, true
         )
         runOnUiThread(
@@ -98,11 +98,9 @@ class BufferListAdapter(
             data.clear()
             data.addAll(new)
             result.dispatchUpdatesTo(this@BufferListAdapter)
-          }
-        )
+          })
       }
-    }
-    )
+    })
   }
 
   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
@@ -140,8 +138,8 @@ class BufferListAdapter(
     )
   }
 
-  override fun onBindViewHolder(holder: BufferViewHolder, position: Int)
-    = holder.bind(data[position].props, data[position].state)
+  override fun onBindViewHolder(holder: BufferViewHolder, position: Int) =
+    holder.bind(data[position].props, data[position].state)
 
   override fun getItemCount() = data.size
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigAdapter.kt
index 53b3cc1edb1c5fd594d06798eb46b3e3f7c8dd8a..72ff5616d1a062cdf097182b76395c15fb272a24 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigAdapter.kt
@@ -31,14 +31,13 @@ class BufferViewConfigAdapter(
         data.addAll(list)
       }
       notifyDataSetChanged()
-    }
-    )
+    })
   }
 
   override fun isEmpty() = data.isEmpty()
 
-  override fun onBindViewHolder(holder: BufferViewConfigViewHolder, position: Int)
-    = holder.bind(getItem(position))
+  override fun onBindViewHolder(holder: BufferViewConfigViewHolder, position: Int) =
+    holder.bind(getItem(position))
 
   override fun onCreateViewHolder(parent: ViewGroup, dropDown: Boolean)
     : BufferViewConfigViewHolder {
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt
index 70b1a339acb1b1148a7039859d52abb0beb787ee..2c398209052f36dc82182c8e90267a1a86623035 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt
@@ -79,7 +79,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
             actionMode?.finish()
             true
           }
-          R.id.action_delete    -> {
+          R.id.action_delete     -> {
             MaterialDialog.Builder(activity!!)
               .content(R.string.buffer_delete_confirmation)
               .positiveText(R.string.label_yes)
@@ -95,7 +95,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
             actionMode?.finish()
             true
           }
-          R.id.action_rename    -> {
+          R.id.action_rename     -> {
             MaterialDialog.Builder(activity!!)
               .input(
                 getString(R.string.label_buffer_name),
@@ -114,21 +114,21 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
             actionMode?.finish()
             true
           }
-          R.id.action_unhide    -> {
+          R.id.action_unhide     -> {
             bufferSyncer?.let {
               bufferViewConfig?.requestAddBuffer(info, bufferSyncer)
             }
             true
           }
-          R.id.action_hide_temp -> {
+          R.id.action_hide_temp  -> {
             bufferViewConfig?.requestRemoveBuffer(info.bufferId)
             true
           }
-          R.id.action_hide_perm -> {
+          R.id.action_hide_perm  -> {
             bufferViewConfig?.requestRemoveBufferPermanently(info.bufferId)
             true
           }
-          else                  -> false
+          else                   -> false
         }
       } else {
         false
@@ -210,9 +210,9 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
             )
           )
         }.filter { props ->
-            minimumActivity.toInt() <= props.bufferActivity.toInt() ||
-            props.info.type.hasFlag(Buffer_Type.StatusBuffer)
-          }
+          minimumActivity.toInt() <= props.bufferActivity.toInt() ||
+          props.info.type.hasFlag(Buffer_Type.StatusBuffer)
+        }
       },
       viewModel.selectedBufferId,
       viewModel.collapsedNetworks,
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/AutoCompleteAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/AutoCompleteAdapter.kt
similarity index 72%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/AutoCompleteAdapter.kt
rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/AutoCompleteAdapter.kt
index f96a87cb483998f9bfb229d097f0b9d86cba7125..62778a67e8307673cbf7cda0b99d4b3256bbed85 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/AutoCompleteAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/AutoCompleteAdapter.kt
@@ -1,10 +1,8 @@
-package de.kuschku.quasseldroid_ng.ui.chat
+package de.kuschku.quasseldroid_ng.ui.chat.input
 
-import android.arch.lifecycle.LifecycleOwner
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.Observer
 import android.graphics.drawable.Drawable
 import android.support.v4.graphics.drawable.DrawableCompat
+import android.support.v7.recyclerview.extensions.ListAdapter
 import android.support.v7.util.DiffUtil
 import android.support.v7.widget.RecyclerView
 import android.view.LayoutInflater
@@ -17,48 +15,22 @@ import butterknife.ButterKnife
 import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.quasseldroid_ng.R
-import de.kuschku.quasseldroid_ng.ui.chat.NickListAdapter.Companion.VIEWTYPE_AWAY
 import de.kuschku.quasseldroid_ng.ui.chat.buffers.BufferListAdapter
+import de.kuschku.quasseldroid_ng.ui.chat.nicks.NickListAdapter.Companion.VIEWTYPE_AWAY
 import de.kuschku.quasseldroid_ng.util.helper.getCompatDrawable
 import de.kuschku.quasseldroid_ng.util.helper.styledAttributes
 import de.kuschku.quasseldroid_ng.util.helper.visibleIf
 
 class AutoCompleteAdapter(
-  lifecycleOwner: LifecycleOwner,
-  liveData: LiveData<Pair<String, List<AutoCompleteItem>>?>,
-  runInBackground: (() -> Unit) -> Any,
-  runOnUiThread: (Runnable) -> Any,
   private val clickListener: ((String) -> Unit)? = null
-) : RecyclerView.Adapter<AutoCompleteAdapter.AutoCompleteViewHolder>() {
-  var data = mutableListOf<AutoCompleteItem>()
-
-  init {
-    liveData.observe(
-      lifecycleOwner, Observer { it: Pair<String, List<AutoCompleteItem>>? ->
-      runInBackground {
-        val word = it?.first ?: ""
-        val list = it?.second ?: emptyList()
-        val old: List<AutoCompleteItem> = data
-        val new: List<AutoCompleteItem> = if (word.length >= 3) list else emptyList()
-        val result = DiffUtil.calculateDiff(
-          object : DiffUtil.Callback() {
-            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
-              old[oldItemPosition].name == new[newItemPosition].name
-
-            override fun getOldListSize() = old.size
-            override fun getNewListSize() = new.size
-            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
-              old[oldItemPosition] == new[newItemPosition]
-          }, true
-        )
-        runOnUiThread(Runnable {
-          data.clear()
-          data.addAll(new)
-          result.dispatchUpdatesTo(this@AutoCompleteAdapter)
-        })
-      }
-    })
-  }
+) : ListAdapter<AutoCompleteAdapter.AutoCompleteItem, AutoCompleteAdapter.AutoCompleteViewHolder>(
+  object : DiffUtil.ItemCallback<AutoCompleteItem>() {
+    override fun areItemsTheSame(oldItem: AutoCompleteItem, newItem: AutoCompleteItem) =
+      oldItem.name == newItem.name
+
+    override fun areContentsTheSame(oldItem: AutoCompleteItem, newItem: AutoCompleteItem) =
+      oldItem == newItem
+  }) {
 
   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
     VIEWTYPE_CHANNEL                         -> AutoCompleteViewHolder.ChannelViewHolder(
@@ -81,11 +53,9 @@ class AutoCompleteAdapter(
   }
 
   override fun onBindViewHolder(holder: AutoCompleteViewHolder, position: Int) =
-    holder.bind(data[position])
-
-  override fun getItemCount() = data.size
+    holder.bind(getItem(position))
 
-  override fun getItemViewType(position: Int) = data[position].let { it ->
+  override fun getItemViewType(position: Int) = getItem(position).let {
     when {
       it is AutoCompleteItem.ChannelItem         -> VIEWTYPE_CHANNEL
       it is AutoCompleteItem.UserItem && it.away -> VIEWTYPE_NICK_AWAY
@@ -96,11 +66,11 @@ class AutoCompleteAdapter(
   sealed class AutoCompleteItem(open val name: String) : Comparable<AutoCompleteItem> {
     override fun compareTo(other: AutoCompleteItem): Int {
       return when {
-        this is AutoCompleteItem.UserItem &&
-        other is AutoCompleteItem.ChannelItem -> -1
-        this is AutoCompleteItem.ChannelItem &&
-        other is AutoCompleteItem.UserItem    -> 1
-        else                                  -> this.name.compareTo(other.name)
+        this is UserItem &&
+        other is ChannelItem -> -1
+        this is ChannelItem &&
+        other is UserItem    -> 1
+        else                 -> this.name.compareTo(other.name)
       }
     }
 
@@ -224,8 +194,8 @@ class AutoCompleteAdapter(
   }
 
   companion object {
-    val VIEWTYPE_CHANNEL = 0
-    val VIEWTYPE_NICK_ACTIVE = 1
-    val VIEWTYPE_NICK_AWAY = 2
+    const val VIEWTYPE_CHANNEL = 0
+    const val VIEWTYPE_NICK_ACTIVE = 1
+    const val VIEWTYPE_NICK_AWAY = 2
   }
 }
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/Editor.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/Editor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d7463e063bceb81a8950d3a800c70b9366e3c808
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/Editor.kt
@@ -0,0 +1,224 @@
+package de.kuschku.quasseldroid_ng.ui.chat.input
+
+import android.arch.lifecycle.LiveData
+import android.arch.lifecycle.MutableLiveData
+import android.arch.lifecycle.Observer
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.widget.*
+import android.text.Editable
+import android.text.InputType
+import android.text.TextWatcher
+import android.view.KeyEvent
+import android.view.MenuItem
+import android.view.inputmethod.EditorInfo
+import de.kuschku.quasseldroid_ng.R
+import de.kuschku.quasseldroid_ng.settings.AppearanceSettings
+import de.kuschku.quasseldroid_ng.settings.Settings
+import de.kuschku.quasseldroid_ng.ui.chat.ChatActivity
+import de.kuschku.quasseldroid_ng.util.helper.lastWordIndices
+import de.kuschku.quasseldroid_ng.util.helper.lineSequence
+import de.kuschku.quasseldroid_ng.util.helper.retint
+import io.reactivex.Observable
+import io.reactivex.subjects.BehaviorSubject
+
+class Editor(
+  // Contexts
+  activity: AppCompatActivity,
+  // LiveData
+  private val autoCompleteData: LiveData<Pair<String, List<AutoCompleteAdapter.AutoCompleteItem>>?>,
+  lastWordContainer: MutableLiveData<Observable<Pair<String, IntRange>>>,
+  // Views
+  val chatline: AppCompatEditText,
+  send: AppCompatImageButton,
+  autoCompleteLists: List<RecyclerView>,
+  formattingMenu: ActionMenuView,
+  formattingToolbar: Toolbar,
+  // Listeners
+  private val sendCallback: (Sequence<Pair<CharSequence, String>>) -> Unit,
+  private val panelStateCallback: (Boolean) -> Unit
+) : ActionMenuView.OnMenuItemClickListener, Toolbar.OnMenuItemClickListener {
+  override fun onMenuItemClick(item: MenuItem?) = when (item?.itemId) {
+    R.id.input_history -> {
+      panelStateCallback(true)
+      true
+    }
+    else               -> formatHandler.onMenuItemClick(item)
+  }
+
+  private val appearanceSettings = Settings.appearance(activity)
+
+  private val lastWord = BehaviorSubject.createDefault(Pair("", IntRange.EMPTY))
+  private val textWatcher = object : TextWatcher {
+    override fun afterTextChanged(s: Editable?) {
+      val previous = autocompletionState
+      val next = if (previous != null && s != null) {
+        val suffix = if (previous.range.start == 0) ": " else " "
+        val end = Math.min(
+          s.length, previous.range.start + previous.completion.name.length + suffix.length
+        )
+        val sequence = s.substring(previous.range.start, end)
+        if (sequence == previous.completion.name + suffix) {
+          previous.originalWord to (previous.range.start until end)
+        } else {
+          autocompletionState = null
+          s.lastWordIndices(chatline.selectionStart, onlyBeforeCursor = true)?.let { indices ->
+            s.substring(indices) to indices
+          }
+        }
+      } else {
+        s?.lastWordIndices(chatline.selectionStart, onlyBeforeCursor = true)?.let { indices ->
+          s.substring(indices) to indices
+        }
+      }
+
+      lastWord.onNext(next ?: Pair("", IntRange.EMPTY))
+    }
+
+    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
+    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
+  }
+
+  val formatHandler = FormatHandler(chatline)
+
+  private var autocompletionState: ChatActivity.AutoCompletionState? = null
+
+  init {
+    send.setOnClickListener {
+      send()
+    }
+
+    chatline.setOnKeyListener { _, keyCode, event ->
+      when (keyCode) {
+        KeyEvent.KEYCODE_ENTER,
+        KeyEvent.KEYCODE_NUMPAD_ENTER -> if (event.hasNoModifiers()) {
+          send()
+          true
+        } else {
+          false
+        }
+        KeyEvent.KEYCODE_TAB          -> {
+          autoComplete(event.isShiftPressed)
+          true
+        }
+        else                          -> false
+      }
+    }
+
+    chatline.imeOptions = when (appearanceSettings.inputEnter) {
+      AppearanceSettings.InputEnterMode.EMOJI -> listOf(
+        EditorInfo.IME_ACTION_NONE,
+        EditorInfo.IME_FLAG_NO_EXTRACT_UI
+      )
+      AppearanceSettings.InputEnterMode.SEND  -> listOf(
+        EditorInfo.IME_ACTION_SEND,
+        EditorInfo.IME_FLAG_NO_EXTRACT_UI
+      )
+    }.fold(0, Int::or)
+
+    chatline.addTextChangedListener(textWatcher)
+
+
+    val autocompleteAdapter = AutoCompleteAdapter(
+      // This is still broken when mixing tab complete and UI auto complete
+      formatHandler::autoComplete
+    )
+
+    autoCompleteData.observe(activity, Observer {
+      val query = it?.first ?: ""
+      val list = if (query.length >= 3) it?.second.orEmpty() else emptyList()
+
+      autocompleteAdapter.submitList(list)
+    })
+
+    if (appearanceSettings.showAutocomplete) {
+      for (autoCompleteList in autoCompleteLists) {
+        autoCompleteList.layoutManager = LinearLayoutManager(activity)
+        autoCompleteList.itemAnimator = DefaultItemAnimator()
+        autoCompleteList.adapter = autocompleteAdapter
+      }
+    }
+
+    lastWordContainer.value = lastWord
+
+    activity.menuInflater.inflate(formatHandler.menu, formattingMenu.menu)
+    formattingMenu.menu.retint(activity)
+    formattingMenu.setOnMenuItemClickListener(this)
+
+    activity.menuInflater.inflate(R.menu.input_panel, formattingToolbar.menu)
+    formattingToolbar.menu.retint(activity)
+    formattingToolbar.setOnMenuItemClickListener(this)
+  }
+
+  private fun send() {
+    if (rawText.isNotBlank()) {
+      sendCallback(strippedText.lineSequence().zip(formattedText))
+    }
+    chatline.setText("")
+  }
+
+  fun setMultiLine(enabled: Boolean) {
+    val selectionStart = chatline.selectionStart
+    val selectionEnd = chatline.selectionEnd
+
+    if (enabled) {
+      chatline.inputType = chatline.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE.inv()
+    } else {
+      chatline.inputType = chatline.inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
+    }
+
+    chatline.setSelection(selectionStart, selectionEnd)
+  }
+
+  private fun autoComplete(reverse: Boolean = false) {
+    val originalWord = lastWord.value
+
+    val previous = autocompletionState
+    if (!originalWord.second.isEmpty()) {
+      val autoCompletedWords = autoCompleteData.value?.second.orEmpty()
+      if (previous != null && lastWord.value.first == previous.originalWord && lastWord.value.second.start == previous.range.start) {
+        val previousIndex = autoCompletedWords.indexOf(previous.completion)
+        val autoCompletedWord = if (previousIndex != -1) {
+          val change = if (reverse) -1 else +1
+          val newIndex = (previousIndex + change + autoCompletedWords.size) % autoCompletedWords.size
+
+          autoCompletedWords[newIndex]
+        } else {
+          autoCompletedWords.firstOrNull()
+        }
+        if (autoCompletedWord != null) {
+          val newState = ChatActivity.AutoCompletionState(
+            previous.originalWord,
+            originalWord.second,
+            previous.completion,
+            autoCompletedWord
+          )
+          autocompletionState = newState
+          formatHandler.autoComplete(newState)
+        } else {
+          autocompletionState = null
+        }
+      } else {
+        val autoCompletedWord = autoCompletedWords.firstOrNull()
+        if (autoCompletedWord != null) {
+          val newState = ChatActivity.AutoCompletionState(
+            originalWord.first,
+            originalWord.second,
+            null,
+            autoCompletedWord
+          )
+          autocompletionState = newState
+          formatHandler.autoComplete(newState)
+        } else {
+          autocompletionState = null
+        }
+      }
+    }
+  }
+
+  val formattedText: Sequence<String>
+    get() = formatHandler.formattedText
+  val rawText: CharSequence
+    get() = formatHandler.rawText
+  val strippedText: CharSequence
+    get() = formatHandler.strippedText
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/InputEditor.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/FormatHandler.kt
similarity index 89%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/InputEditor.kt
rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/FormatHandler.kt
index 82aac52f62a00edb552ea886d65f4506ba2dac21..71502038656472a1758b0e5cf23f3c74da95f5b0 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/InputEditor.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/FormatHandler.kt
@@ -1,8 +1,9 @@
-package de.kuschku.quasseldroid_ng.ui.chat
+package de.kuschku.quasseldroid_ng.ui.chat.input
 
 import android.graphics.Typeface
 import android.support.annotation.MenuRes
 import android.text.Editable
+import android.text.SpannableString
 import android.text.Spanned
 import android.text.style.StrikethroughSpan
 import android.text.style.StyleSpan
@@ -11,15 +12,33 @@ import android.text.style.UnderlineSpan
 import android.view.MenuItem
 import android.widget.EditText
 import de.kuschku.quasseldroid_ng.R
+import de.kuschku.quasseldroid_ng.ui.chat.ChatActivity
 import de.kuschku.quasseldroid_ng.util.helper.lastWordIndices
+import de.kuschku.quasseldroid_ng.util.helper.lineSequence
 import de.kuschku.quasseldroid_ng.util.helper.selection
 import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatSerializer
 import de.kuschku.quasseldroid_ng.util.irc.format.spans.*
 
-class InputEditor(private val editText: EditText) {
+class FormatHandler(private val editText: EditText) {
   private val serializer = IrcFormatSerializer(editText.context)
-  val formattedString: String
-    get() = serializer.toEscapeCodes(editText.text)
+  val formattedText: Sequence<String>
+    get() = editText.text.lineSequence().map { serializer.toEscapeCodes(SpannableString(it)) }
+  val rawText: CharSequence
+    get() = editText.text
+  val strippedText: CharSequence
+    get() = editText.text.let {
+      val text = SpannableString(it)
+      val toRemove = mutableListOf<Any>()
+      for (span in text.getSpans(0, text.length, Any::class.java)) {
+        if ((text.getSpanFlags(span) and Spanned.SPAN_COMPOSING) != 0) {
+          toRemove.add(span)
+        }
+      }
+      for (span in toRemove) {
+        text.removeSpan(span)
+      }
+      text
+    }
 
   @MenuRes
   val menu: Int = R.menu.editor
@@ -243,7 +262,7 @@ class InputEditor(private val editText: EditText) {
     }
   }
 
-  fun share(text: CharSequence?) {
+  fun replace(text: CharSequence?) {
     editText.setText(text)
     editText.setSelection(editText.text.length)
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/MessageHistoryAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/MessageHistoryAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8c784e2752e116ed748e30a54f25c2e7a8f1f8f3
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/input/MessageHistoryAdapter.kt
@@ -0,0 +1,57 @@
+package de.kuschku.quasseldroid_ng.ui.chat.input
+
+import android.support.v7.recyclerview.extensions.ListAdapter
+import android.support.v7.util.DiffUtil
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import butterknife.BindView
+import butterknife.ButterKnife
+import de.kuschku.quasseldroid_ng.R
+
+class MessageHistoryAdapter(
+  private val clickListener: ((CharSequence) -> Unit)? = null
+) : ListAdapter<CharSequence, MessageHistoryAdapter.MessageViewHolder>(
+  object : DiffUtil.ItemCallback<CharSequence>() {
+    override fun areItemsTheSame(oldItem: CharSequence?, newItem: CharSequence?) =
+      oldItem === newItem
+
+    override fun areContentsTheSame(oldItem: CharSequence?, newItem: CharSequence?) =
+      oldItem == newItem
+  }) {
+  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+    MessageViewHolder(
+      LayoutInflater.from(parent.context).inflate(R.layout.widget_history_message, parent, false),
+      clickListener = clickListener
+    )
+
+  override fun onBindViewHolder(holder: MessageViewHolder, position: Int) =
+    holder.bind(getItem(position))
+
+  class MessageViewHolder(
+    itemView: View,
+    private val clickListener: ((CharSequence) -> Unit)? = null
+  ) : RecyclerView.ViewHolder(itemView) {
+    @BindView(R.id.content)
+    lateinit var content: TextView
+
+    var value: CharSequence? = null
+
+    init {
+      ButterKnife.bind(this, itemView)
+      itemView.setOnClickListener {
+        val value = value
+        if (value != null)
+          clickListener?.invoke(value)
+      }
+    }
+
+    fun bind(data: CharSequence) {
+      value = data
+
+      content.text = data
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageAdapter.kt
index 57bbb6fd7172ffc0d2d4cd519482ec2b67f9e73e..1eb2082c2b26fda9a7b4178d014463c5a3d889be 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageAdapter.kt
@@ -8,7 +8,6 @@ import android.view.LayoutInflater
 import android.view.ViewGroup
 import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.util.hasFlag
-import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase.DatabaseMessage
 import de.kuschku.quasseldroid_ng.settings.AppearanceSettings
 import de.kuschku.quasseldroid_ng.util.helper.getOrPut
@@ -17,19 +16,16 @@ class MessageAdapter(
   context: Context,
   appearanceSettings: AppearanceSettings,
   var markerLinePosition: Pair<MsgId, MsgId>? = null
-) : PagedListAdapter<QuasselDatabase.DatabaseMessage, QuasselMessageViewHolder>(
-  object : DiffUtil.ItemCallback<QuasselDatabase.DatabaseMessage>() {
-    override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage,
-                                 newItem: QuasselDatabase.DatabaseMessage) =
-      DatabaseMessage.MessageDiffCallback.areItemsTheSame(oldItem, newItem)
+) : PagedListAdapter<DatabaseMessage, QuasselMessageViewHolder>(
+  object : DiffUtil.ItemCallback<DatabaseMessage>() {
+    override fun areItemsTheSame(oldItem: DatabaseMessage, newItem: DatabaseMessage) =
+      oldItem.messageId == newItem.messageId
 
-    override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage,
-                                    newItem: QuasselDatabase.DatabaseMessage) =
-      DatabaseMessage.MessageDiffCallback.areContentsTheSame(oldItem, newItem) &&
+    override fun areContentsTheSame(oldItem: DatabaseMessage, newItem: DatabaseMessage) =
+      oldItem == newItem &&
       oldItem.messageId != markerLinePosition?.first &&
       oldItem.messageId != markerLinePosition?.second
-  }
-) {
+  }) {
   private val messageRenderer: MessageRenderer = QuasselMessageRenderer(
     context,
     appearanceSettings
@@ -53,8 +49,7 @@ class MessageAdapter(
           messageCache.getOrPut(it.messageId) {
             messageRenderer.render(it, markerLinePosition?.second ?: -1)
           }
-        }
-      )
+        })
     }
   }
 
@@ -67,13 +62,12 @@ class MessageAdapter(
     }
   }
 
-  private fun viewType(type: Message_Types, flags: Message_Flags): Int {
+  private fun viewType(type: Message_Types, flags: Message_Flags) =
     if (flags.hasFlag(Message_Flag.Highlight)) {
-      return -type.value
+      -type.value
     } else {
-      return type.value
+      type.value
     }
-  }
 
   override fun getItemId(position: Int): Long {
     return getItem(position)?.messageId?.toLong() ?: 0L
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageListFragment.kt
index 7513fbe59497b541256a0c52c7c4885e71775706..49a76a1e9f289781e02eec13d300c6f40c5bd742 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/MessageListFragment.kt
@@ -84,8 +84,7 @@ class MessageListFragment : ServiceBoundFragment() {
           scrollDown.visibility = View.VISIBLE
           scrollDown.toggle(canScrollDown && isScrollingDown)
         }
-      }
-    )
+      })
 
     database = QuasselDatabase.Creator.init(context!!.applicationContext)
     val data = viewModel.getBuffer().switchMapNotNull { buffer ->
@@ -136,16 +135,14 @@ class MessageListFragment : ServiceBoundFragment() {
             lastBuffer = buffer
           }
         }
-      }
-      )
+      })
     }
 
     viewModel.markerLine.observe(
       this, Observer {
       adapter.markerLinePosition = it
       adapter.notifyDataSetChanged()
-    }
-    )
+    })
 
     var lastBuffer = -1
     data.observe(
@@ -161,8 +158,7 @@ class MessageListFragment : ServiceBoundFragment() {
         activity?.runOnUiThread { messageList.scrollToPosition(0) }
         handler.postDelayed({ activity?.runOnUiThread { messageList.scrollToPosition(0) } }, 16)
       }
-    }
-    )
+    })
     scrollDown.hide()
     scrollDown.setOnClickListener { messageList.scrollToPosition(0) }
     return view
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/QuasselMessageRenderer.kt
index 99c5e96133425ef196d79e3ec6d7e535dae4abab..74836d7b1a4a8fd31322626d983e1c60a5fcd0cf 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/QuasselMessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/messages/QuasselMessageRenderer.kt
@@ -333,8 +333,7 @@ class QuasselMessageRenderer(
     }
     /*
     for (result in channelPattern.findAll(content)) {
-      text.setSpan(URLSpan(result.value), result.range.start, result.range.endInclusive, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
-    }
+      text.setSpan(URLSpan(result.value), result.range.start, result.range.endInclusive, Spanned.SPAN_INCLUSIVE_INCLUSIVE)}
     */
 
     return text
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicks/NickListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicks/NickListAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2b1a21ee64e6bde3ece12b3b87e386de6a9d6654
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicks/NickListAdapter.kt
@@ -0,0 +1,96 @@
+package de.kuschku.quasseldroid_ng.ui.chat.nicks
+
+import android.support.v7.recyclerview.extensions.ListAdapter
+import android.support.v7.util.DiffUtil
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import butterknife.BindView
+import butterknife.ButterKnife
+import de.kuschku.quasseldroid_ng.R
+import de.kuschku.quasseldroid_ng.util.helper.visibleIf
+
+class NickListAdapter(
+  private val clickListener: ((String) -> Unit)? = null
+) : ListAdapter<NickListAdapter.IrcUserItem, NickListAdapter.NickViewHolder>(
+  object : DiffUtil.ItemCallback<IrcUserItem>() {
+    override fun areItemsTheSame(oldItem: IrcUserItem, newItem: IrcUserItem) =
+      oldItem.nick == newItem.nick
+
+    override fun areContentsTheSame(oldItem: IrcUserItem?, newItem: IrcUserItem?) =
+      oldItem == newItem
+  }) {
+  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+    NickViewHolder(
+      LayoutInflater.from(parent.context).inflate(
+        when (viewType) {
+          VIEWTYPE_AWAY -> R.layout.widget_nick_away
+          else          -> R.layout.widget_nick
+        }, parent, false
+      ),
+      clickListener = clickListener
+    )
+
+  override fun onBindViewHolder(holder: NickViewHolder, position: Int) =
+    holder.bind(getItem(position))
+
+  override fun getItemViewType(position: Int) = if (getItem(position).away) {
+    VIEWTYPE_AWAY
+  } else {
+    VIEWTYPE_ACTIVE
+  }
+
+  data class IrcUserItem(
+    val nick: String,
+    val modes: String,
+    val lowestMode: Int,
+    val realname: CharSequence,
+    val away: Boolean,
+    val networkCasemapping: String
+  )
+
+  class NickViewHolder(
+    itemView: View,
+    private val clickListener: ((String) -> Unit)? = null
+  ) : RecyclerView.ViewHolder(itemView) {
+    @BindView(R.id.modesContainer)
+    lateinit var modesContainer: View
+
+    @BindView(R.id.modes)
+    lateinit var modes: TextView
+
+    @BindView(R.id.nick)
+    lateinit var nick: TextView
+
+    @BindView(R.id.realname)
+    lateinit var realname: TextView
+
+    var user: String? = null
+
+    init {
+      ButterKnife.bind(this, itemView)
+      itemView.setOnClickListener {
+        val nick = user
+        if (nick != null)
+          clickListener?.invoke(nick)
+      }
+    }
+
+    fun bind(data: IrcUserItem) {
+      user = data.nick
+
+      nick.text = data.nick
+      modes.text = data.modes
+      realname.text = data.realname
+
+      modes.visibleIf(data.modes.isNotBlank())
+    }
+  }
+
+  companion object {
+    val VIEWTYPE_ACTIVE = 0
+    val VIEWTYPE_AWAY = 1
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicks/NickListFragment.kt
similarity index 69%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt
rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicks/NickListFragment.kt
index 5ff1deaa63b5a14f26818c1659bfaf284beefa41..ba8d29d332b393a6720b2d43774e59f5d56698c1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/nicks/NickListFragment.kt
@@ -1,5 +1,6 @@
-package de.kuschku.quasseldroid_ng.ui.chat
+package de.kuschku.quasseldroid_ng.ui.chat.nicks
 
+import android.arch.lifecycle.Observer
 import android.arch.lifecycle.ViewModelProviders
 import android.os.Bundle
 import android.support.v7.widget.DefaultItemAnimator
@@ -10,6 +11,7 @@ import android.view.View
 import android.view.ViewGroup
 import butterknife.BindView
 import butterknife.ButterKnife
+import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.settings.AppearanceSettings
 import de.kuschku.quasseldroid_ng.settings.Settings
@@ -47,32 +49,29 @@ class NickListFragment : ServiceBoundFragment() {
     val view = inflater.inflate(R.layout.fragment_nick_list, container, false)
     ButterKnife.bind(this, view)
 
-    nickList.adapter = NickListAdapter(
-      this,
-      viewModel.nickData.map {
-        it.map {
-          it.copy(
-            modes = when (appearanceSettings.showPrefix) {
-              AppearanceSettings.ShowPrefixMode.ALL -> it.modes
-              else                                  -> it.modes.substring(
-                0, Math.min(
-                it.modes.length, 1
-              )
-              )
-            },
-            realname = ircFormatDeserializer?.formatString(
-              it.realname.toString(), appearanceSettings.colorizeMirc
-            ) ?: it.realname
-          )
-        }
-      },
-      handlerThread::post,
-      activity!!::runOnUiThread,
-      clickListener
-    )
-
+    val nickListAdapter = NickListAdapter(clickListener)
+    nickList.adapter = nickListAdapter
     nickList.layoutManager = LinearLayoutManager(context)
     nickList.itemAnimator = DefaultItemAnimator()
+    viewModel.nickData.map {
+      it.map {
+        it.copy(
+          modes = when (appearanceSettings.showPrefix) {
+            AppearanceSettings.ShowPrefixMode.ALL ->
+              it.modes
+            else                                  ->
+              it.modes.substring(0, Math.min(it.modes.length, 1))
+          },
+          realname = ircFormatDeserializer?.formatString(
+            it.realname.toString(), appearanceSettings.colorizeMirc
+          ) ?: it.realname
+        )
+      }.sortedBy {
+        IrcCaseMappers[it.networkCasemapping].toLowerCase(it.nick)
+      }.sortedBy {
+        it.lowestMode
+      }
+    }.observe(this, Observer(nickListAdapter::submitList))
 
     return view
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt
index 8ff4782130e2cf162e5752af6694eeab6f4367d6..1ec6a99efbf8480a334d6353a039f8f95f02b0cf 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt
@@ -63,8 +63,8 @@ abstract class SetupActivity : AppCompatActivity() {
     currentPage.value?.requestFocus()
   }
 
-  fun updateRecentsHeader()
-    = updateRecentsHeaderIfExisting(title.toString(), icon, recentsHeaderColor)
+  fun updateRecentsHeader() =
+    updateRecentsHeaderIfExisting(title.toString(), icon, recentsHeaderColor)
 
   override fun setTitle(title: CharSequence?) {
     super.setTitle(title)
@@ -96,8 +96,7 @@ abstract class SetupActivity : AppCompatActivity() {
         button.hide()
         adapter.lastValidItem = viewPager.currentItem - 1
       }
-    }
-    )
+    })
     viewPager.addOnPageChangeListener(pageChangeListener)
     pageChanged()
     updateRecentsHeader()
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt
index e564e80a50e3dce28986b43277affd9a95b4ea76..a7e2a41da8e03844828779b178266a74497981c4 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt
@@ -82,29 +82,26 @@ class AccountEditActivity : AppCompatActivity() {
     nameValidator = object : TextValidator(
       nameWrapper::setError, resources.getString(R.string.hintInvalidName)
     ) {
-      override fun validate(text: Editable)
-        = text.isNotBlank()
+      override fun validate(text: Editable) = text.isNotBlank()
     }
 
     hostValidator = object : TextValidator(
       hostWrapper::setError, resources.getString(R.string.hintInvalidHost)
     ) {
-      override fun validate(text: Editable)
-        = text.toString().matches(Patterns.DOMAIN_NAME.toRegex())
+      override fun validate(text: Editable) =
+        text.toString().matches(Patterns.DOMAIN_NAME.toRegex())
     }
 
     portValidator = object : TextValidator(
       portWrapper::setError, resources.getString(R.string.hintInvalidPort)
     ) {
-      override fun validate(text: Editable)
-        = text.toString().toIntOrNull() in (0 until 65536)
+      override fun validate(text: Editable) = text.toString().toIntOrNull() in (0 until 65536)
     }
 
     userValidator = object : TextValidator(
       userWrapper::setError, resources.getString(R.string.hintInvalidUser)
     ) {
-      override fun validate(text: Editable)
-        = text.isNotBlank()
+      override fun validate(text: Editable) = text.isNotBlank()
     }
 
     name.addTextChangedListener(nameValidator)
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt
index e9fdc5a9b99d6c4199a9360b09bcc3ee63b5756a..23a837b86ab086d0b7f41993d48d6a94dbc55115 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt
@@ -53,16 +53,15 @@ class AccountSetupConnectionSlide : SlideFragment() {
     hostValidator = object : TextValidator(
       hostWrapper::setError, resources.getString(R.string.hintInvalidHost)
     ) {
-      override fun validate(text: Editable)
-        = text.toString().matches(Patterns.DOMAIN_NAME.toRegex())
+      override fun validate(text: Editable) =
+        text.toString().matches(Patterns.DOMAIN_NAME.toRegex())
 
       override fun onChanged() = updateValidity()
     }
     portValidator = object : TextValidator(
       portWrapper::setError, resources.getString(R.string.hintInvalidPort)
     ) {
-      override fun validate(text: Editable)
-        = text.toString().toIntOrNull() in (0 until 65536)
+      override fun validate(text: Editable) = text.toString().toIntOrNull() in (0 until 65536)
 
       override fun onChanged() = updateValidity()
     }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt
index 2e0f8c4298e590a881ef21d1d9470a4febe2f3d9..ab839228c781ab8bdb53e0549944d917e18fb2e2 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt
@@ -43,8 +43,7 @@ class AccountSetupNameSlide : SlideFragment() {
     nameValidator = object : TextValidator(
       nameWrapper::setError, resources.getString(R.string.hintInvalidName)
     ) {
-      override fun validate(text: Editable)
-        = text.isNotBlank()
+      override fun validate(text: Editable) = text.isNotBlank()
 
       override fun onChanged() = updateValidity()
     }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt
index f995d230fbff84b189fa53a4a7fb7b86183b306b..b9fa2556eb414646dccfe2761d8c5439ea6ef81a 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt
@@ -50,8 +50,7 @@ class AccountSetupUserSlide : SlideFragment() {
     userValidator = object : TextValidator(
       userWrapper::setError, resources.getString(R.string.hintInvalidUser)
     ) {
-      override fun validate(text: Editable)
-        = text.isNotBlank()
+      override fun validate(text: Editable) = text.isNotBlank()
 
       override fun onChanged() = updateValidity()
     }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt
index 20971dc9e7f2a414c13b1432014951e9ca3a9f48..fa61f0f236187782d655769fd66b4d27c7cec2ed 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt
@@ -16,11 +16,11 @@ import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.util.and
 import de.kuschku.libquassel.util.hasFlag
-import de.kuschku.quasseldroid_ng.ui.chat.AutoCompleteAdapter
-import de.kuschku.quasseldroid_ng.ui.chat.NickListAdapter
 import de.kuschku.quasseldroid_ng.ui.chat.ToolbarFragment
 import de.kuschku.quasseldroid_ng.ui.chat.buffers.BufferListAdapter
 import de.kuschku.quasseldroid_ng.ui.chat.buffers.BufferViewConfigFragment
+import de.kuschku.quasseldroid_ng.ui.chat.input.AutoCompleteAdapter
+import de.kuschku.quasseldroid_ng.ui.chat.nicks.NickListAdapter
 import de.kuschku.quasseldroid_ng.util.helper.*
 import io.reactivex.Observable
 import java.util.concurrent.TimeUnit
@@ -184,8 +184,7 @@ class QuasselViewModel : ViewModel() {
                   }
                 }
               }
-            }
-          )
+            })
         }
       } else {
         Observable.just(emptyList())
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/Patterns.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/Patterns.kt
index 4ae713468fc78f9eed13e71c16ac14b87db80183..52ce7acf83e340f13be5ffcf9f8e232a4677f176 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/Patterns.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/Patterns.kt
@@ -6,58 +6,47 @@ import java.util.regex.Pattern
 @SuppressWarnings("Access")
 object Patterns {
   @Language("RegExp")
-  const val IPv4
-    = "(?:(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])\\.){3}(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])"
+  const val IPv4 = "(?:(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])\\.){3}(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])"
 
   @Language("RegExp")
-  const val IPv6
-    = "(?:(?:(?:[0-9a-fA-F]{1,4}:){7}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,7}|:):(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,6}|:):(?:[0-9a-fA-F]{1,4}:)?(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,5}|:):(?:[0-9a-fA-F]{1,4}:){0,2}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,4}|:):(?:[0-9a-fA-F]{1,4}:){0,3}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,3}|:):(?:[0-9a-fA-F]{1,4}:){1,4}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,2}|:):(?:[0-9a-fA-F]{1,4}:){0,5}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:)|:):(?:[0-9a-fA-F]{1,4}:){0,6}(?:[0-9a-fA-F]{1,4})))"
+  const val IPv6 = "(?:(?:(?:[0-9a-fA-F]{1,4}:){7}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,7}|:):(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,6}|:):(?:[0-9a-fA-F]{1,4}:)?(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,5}|:):(?:[0-9a-fA-F]{1,4}:){0,2}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,4}|:):(?:[0-9a-fA-F]{1,4}:){0,3}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,3}|:):(?:[0-9a-fA-F]{1,4}:){1,4}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,2}|:):(?:[0-9a-fA-F]{1,4}:){0,5}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:)|:):(?:[0-9a-fA-F]{1,4}:){0,6}(?:[0-9a-fA-F]{1,4})))"
 
   @Language("RegExp")
   const val IP_ADDRESS_STRING = """(?:$IPv4|$IPv6)"""
 
   @Language("RegExp")
-  const val UCS_CHAR
-    = "[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]"
+  const val UCS_CHAR = "[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]"
   /**
    * Valid characters for IRI label defined in RFC 3987.
    */
   @Language("RegExp")
-  const val LABEL_CHAR
-    = """a-zA-Z0-9$UCS_CHAR"""
+  const val LABEL_CHAR = """a-zA-Z0-9$UCS_CHAR"""
   /**
    * Valid characters for IRI TLD defined in RFC 3987.
    */
   @Language("RegExp")
-  const val TLD_CHAR
-    = """a-zA-Z$UCS_CHAR"""
+  const val TLD_CHAR = """a-zA-Z$UCS_CHAR"""
   /**
    * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets.
    */
   @Language("RegExp")
-  const val IRI_LABEL
-    = """[$LABEL_CHAR](?:[${LABEL_CHAR}_\-]{0,61}[$LABEL_CHAR])?"""
+  const val IRI_LABEL = """[$LABEL_CHAR](?:[${LABEL_CHAR}_\-]{0,61}[$LABEL_CHAR])?"""
   /**
    * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters.
    */
   @Language("RegExp")
-  const val PUNYCODE_TLD
-    = """xn--[\w\-]{0,58}\w"""
+  const val PUNYCODE_TLD = """xn--[\w\-]{0,58}\w"""
 
   @Language("RegExp")
-  const val TLD
-    = """(?:$PUNYCODE_TLD|[$TLD_CHAR]{2,63})"""
+  const val TLD = """(?:$PUNYCODE_TLD|[$TLD_CHAR]{2,63})"""
 
   @Language("RegExp")
-  const val HOST_NAME
-    = """(?:$IRI_LABEL\.)+$TLD.?"""
+  const val HOST_NAME = """(?:$IRI_LABEL\.)+$TLD.?"""
 
   @Language("RegExp")
-  const val LOCAL_HOST_NAME
-    = """(?:$IRI_LABEL\.)*$IRI_LABEL"""
+  const val LOCAL_HOST_NAME = """(?:$IRI_LABEL\.)*$IRI_LABEL"""
 
   @Language("RegExp")
-  const val DOMAIN_NAME_STR
-    = """(?:$LOCAL_HOST_NAME|$HOST_NAME|$IP_ADDRESS_STRING)"""
+  const val DOMAIN_NAME_STR = """(?:$LOCAL_HOST_NAME|$HOST_NAME|$IP_ADDRESS_STRING)"""
   val DOMAIN_NAME: Pattern = Pattern.compile(DOMAIN_NAME_STR)
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt
index 4d656cc54e30e11d38a3127dcc5d3831efc090b4..f62f03f2cf0773d8a39054dff437f9013f3d8aff 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt
@@ -18,11 +18,12 @@ fun Context.getStatusBarHeight(): Int {
   return result
 }
 
-inline fun <reified T> Context.systemService(): T = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-  getSystemService(T::class.java)
-} else {
-  getSystemService(T::class.java.simpleName) as T
-}
+inline fun <reified T> Context.systemService(): T =
+  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+    getSystemService(T::class.java)
+  } else {
+    getSystemService(T::class.java.simpleName) as T
+  }
 
 fun Context.getCompatDrawable(@DrawableRes id: Int): Drawable {
   return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt
index 5c81c989806fd9c0659e7c19ce54769765e8bbff..b44582900bdbb0d4f45c9658d3f6f12df70717cf 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt
@@ -27,8 +27,7 @@ inline fun <X, Y> LiveData<X?>.switchMap(
         result.value = null
       }
     }
-  }
-  )
+  })
   return result
 }
 
@@ -54,8 +53,7 @@ inline fun <X, Y> LiveData<X>.switchMapNotNull(
         result.value = null
       }
     }
-  }
-  )
+  })
   return result
 }
 
@@ -82,8 +80,7 @@ inline fun <X, Y> LiveData<X?>.switchMapRx(
         result.value = null
       }
     }
-  }
-  )
+  })
   return result
 }
 
@@ -139,8 +136,8 @@ inline fun <T> LiveData<T>.observeForeverSticky(observer: Observer<T>) {
   observer.onChanged(value)
 }
 
-inline fun <T> LiveData<T>.toObservable(lifecycleOwner: LifecycleOwner): Observable<T>
-  = Observable.fromPublisher(LiveDataReactiveStreams.toPublisher(lifecycleOwner, this))
+inline fun <T> LiveData<T>.toObservable(lifecycleOwner: LifecycleOwner): Observable<T> =
+  Observable.fromPublisher(LiveDataReactiveStreams.toPublisher(lifecycleOwner, this))
 
 
 inline operator fun <T> LiveData<T>.invoke() = value
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MenuHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MenuHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d2d70dcb4a87e7442964c6bb3b25fb8cc6c7a8a0
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MenuHelper.kt
@@ -0,0 +1,18 @@
+package de.kuschku.quasseldroid_ng.util.helper
+
+import android.content.Context
+import android.support.v4.graphics.drawable.DrawableCompat
+import android.view.Menu
+import de.kuschku.quasseldroid_ng.R
+
+fun Menu.retint(context: Context) {
+  context.theme.styledAttributes(R.attr.colorControlNormal) {
+    val color = getColor(0, 0)
+
+    for (item in (0 until size()).map { getItem(it) }) {
+      val drawable = item.icon.mutate()
+      DrawableCompat.setTint(drawable, color)
+      item.icon = drawable
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ThemeHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ThemeHelper.kt
index 137e10e6d36ed942b61d09c6279610ca1bc61a9a..3b62d67e5a900536cedafd051b5e1d76a1c0eed5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ThemeHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ThemeHelper.kt
@@ -3,10 +3,10 @@ package de.kuschku.quasseldroid_ng.util.helper
 import android.content.res.Resources
 import android.content.res.TypedArray
 
-inline fun <R> Resources.Theme.styledAttributes(vararg attributes: Int, f: TypedArray.() -> R)
-  = this.obtainStyledAttributes(attributes).run {
-  f()
-}
+inline fun <R> Resources.Theme.styledAttributes(vararg attributes: Int, f: TypedArray.() -> R) =
+  this.obtainStyledAttributes(attributes).run {
+    f()
+  }
 
 inline fun <R> TypedArray.use(block: (TypedArray) -> R): R {
   val result = block(this)
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt
index 51035256600bbda617cd635e0fa30b07a70b3941..641daf540b4a03abe7ac31bc26f536208603c6bc 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt
@@ -90,7 +90,7 @@ class IrcFormatDeserializer(private val context: Context) {
     while (i < str.length) {
       val character = str[i]
       when (character) {
-        CODE_BOLD          -> {
+        CODE_BOLD      -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
@@ -116,7 +116,7 @@ class IrcFormatDeserializer(private val context: Context) {
             italic = FormatDescription(plainText.length, ItalicIrcFormat())
           }
         }
-        CODE_UNDERLINE     -> {
+        CODE_UNDERLINE -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
@@ -241,7 +241,7 @@ class IrcFormatDeserializer(private val context: Context) {
             )
           }
         }
-        CODE_RESET         -> {
+        CODE_RESET     -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt
index ed35f704fbcee23297c5d095b4dbb17e4cbb22cc..62a2351b063a7ab41b87e4817cc9cd10c9cd3081 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt
@@ -49,8 +49,8 @@ abstract class ServiceBoundActivity : AppCompatActivity(),
     updateRecentsHeader()
   }
 
-  fun updateRecentsHeader()
-    = updateRecentsHeaderIfExisting(title.toString(), icon, recentsHeaderColor)
+  fun updateRecentsHeader() =
+    updateRecentsHeaderIfExisting(title.toString(), icon, recentsHeaderColor)
 
   override fun setTitle(title: CharSequence?) {
     super.setTitle(title)
diff --git a/app/src/main/res/layout-sw720dp-land/activity_main.xml b/app/src/main/res/layout-sw720dp-land/activity_main.xml
index 7af3ba7a1e84bea436eefcdf583232c95143c7c9..3e3145e8d8cfb32e18e83c8be70cf3270e7d3732 100644
--- a/app/src/main/res/layout-sw720dp-land/activity_main.xml
+++ b/app/src/main/res/layout-sw720dp-land/activity_main.xml
@@ -47,7 +47,7 @@
 
   <fragment
     android:id="@+id/fragment_nick_list"
-    android:name="de.kuschku.quasseldroid_ng.ui.chat.NickListFragment"
+    android:name="de.kuschku.quasseldroid_ng.ui.chat.nicks.NickListFragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_gravity="end"
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 6df810bd322bf94d06129e5abfe9534c665023b7..908b2b8d77b8defbb261899531d8e7693db4ee00 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -10,7 +10,7 @@
 
   <fragment
     android:id="@+id/fragment_nick_list"
-    android:name="de.kuschku.quasseldroid_ng.ui.chat.NickListFragment"
+    android:name="de.kuschku.quasseldroid_ng.ui.chat.nicks.NickListFragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_gravity="end"
diff --git a/app/src/main/res/layout/layout_editor.xml b/app/src/main/res/layout/layout_editor.xml
index c98d345702e6e792f1256380bdc9e362cacc1d22..8af1417d769c33906fb2029b2f70c64e0c3215b5 100644
--- a/app/src/main/res/layout/layout_editor.xml
+++ b/app/src/main/res/layout/layout_editor.xml
@@ -8,7 +8,7 @@
     android:id="@+id/chatline_scroller"
     android:layout_width="0dp"
     android:layout_height="0dp"
-    app:layout_constraintBottom_toTopOf="@+id/autocomplete_list2"
+    app:layout_constraintBottom_toTopOf="@+id/autocomplete_list_expanded"
     app:layout_constraintEnd_toStartOf="@+id/send"
     app:layout_constraintHorizontal_bias="1.0"
     app:layout_constraintStart_toStartOf="parent"
@@ -30,7 +30,6 @@
       android:paddingTop="8dp"
       android:textColor="?attr/colorForeground"
       android:textSize="16sp" />
-
   </ScrollView>
 
   <android.support.v7.widget.AppCompatImageButton
@@ -47,7 +46,7 @@
     app:srcCompat="@drawable/ic_send" />
 
   <de.kuschku.quasseldroid_ng.util.ui.AutoCompleteRecyclerView
-    android:id="@+id/autocomplete_list2"
+    android:id="@+id/autocomplete_list_expanded"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     app:layout_constraintBottom_toTopOf="@+id/formatting_toolbar_container" />
@@ -65,12 +64,16 @@
       android:layout_width="match_parent"
       android:layout_height="?attr/actionBarSize">
 
-      <android.support.v7.widget.ActionMenuView
-        android:id="@+id/formatting_menu"
+      <HorizontalScrollView
         android:layout_width="match_parent"
-        android:layout_height="?attr/actionBarSize" />
+        android:layout_height="match_parent">
 
+        <android.support.v7.widget.ActionMenuView
+          android:id="@+id/formatting_menu"
+          android:layout_width="wrap_content"
+          android:layout_height="match_parent"
+          android:theme="?attr/formatBarTheme" />
+      </HorizontalScrollView>
     </android.support.v7.widget.Toolbar>
-
   </android.support.design.widget.AppBarLayout>
 </android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/menu/editor.xml b/app/src/main/res/menu/editor.xml
index f50026af71408b0a2a1f2274792e05ed32899eae..3add5e54ebb575acc1bd45651ab500e74edca737 100644
--- a/app/src/main/res/menu/editor.xml
+++ b/app/src/main/res/menu/editor.xml
@@ -26,7 +26,6 @@
     android:icon="@drawable/ic_format_monospace"
     android:title="@string/label_monospace"
     app:showAsAction="always" />
-  <!--
   <item
     android:id="@+id/format_foreground"
     android:icon="@drawable/ic_format_foreground"
@@ -37,7 +36,6 @@
     android:icon="@drawable/ic_format_background"
     android:title="@string/label_background"
     app:showAsAction="always" />
-  -->
   <item
     android:id="@+id/format_clear"
     android:icon="@drawable/ic_format_clear"
diff --git a/app/src/main/res/values-sw720dp/bools.xml b/app/src/main/res/values-sw720dp/bools.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4faec7d15be37131fb8559db83c442574c9adbe7
--- /dev/null
+++ b/app/src/main/res/values-sw720dp/bools.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <bool name="buffer_drawer_exists">false</bool>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d56dfc8c3474f259dec7ec8b5f638170fd257831
--- /dev/null
+++ b/app/src/main/res/values/bools.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <bool name="buffer_drawer_exists">true</bool>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_preferences.xml b/app/src/main/res/values/strings_preferences.xml
index d9d856e155f04d55da5850d063ad73e16e659793..4fac01a4fc27f604ec222e992ac6f8ca2eda7c86 100644
--- a/app/src/main/res/values/strings_preferences.xml
+++ b/app/src/main/res/values/strings_preferences.xml
@@ -106,9 +106,9 @@
 
   <string name="preference_backlog_title">Backlog</string>
 
-  <string name="preference_dynamic_fetch_key" translatable="false">dynamic_fetch</string>
-  <string name="preference_dynamic_fetch_title">Dynamic Fetch Amount</string>
-  <string name="preference_dynamic_fetch_summary">The number of backlog messages to fetch each time</string>
+  <string name="preference_page_size_key" translatable="false">page_size</string>
+  <string name="preference_page_size_title">Page Size</string>
+  <string name="preference_page_size_summary">The number of messages loaded at a time</string>
 
 
   <string name="preference_connection_title">Connection</string>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index c9747aaabd10a690a7f0dc6fa1659505788a717c..ab9de71809071bb655df85eb7ed6362b5b48268d 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -79,11 +79,11 @@
 
   <PreferenceCategory android:title="@string/preference_backlog_title">
     <EditTextPreference
-      android:defaultValue="20"
+      android:defaultValue="150"
       android:inputType="number"
-      android:key="@string/preference_dynamic_fetch_key"
-      android:summary="@string/preference_dynamic_fetch_summary"
-      android:title="@string/preference_dynamic_fetch_title" />
+      android:key="@string/preference_page_size_key"
+      android:summary="@string/preference_page_size_summary"
+      android:title="@string/preference_page_size_title" />
   </PreferenceCategory>
 
   <PreferenceCategory android:title="@string/preference_connection_title">