diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fbcb8b236714ac4f0a07171afcd4bbfdbc7c9863..f9a4c52a00e32f7e4684776b80f79db6927e1f31 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -137,6 +137,7 @@ dependencies {
   // UI
   implementation("me.zhanghai.android.materialprogressbar", "library", "1.4.2")
   implementation("com.afollestad.material-dialogs", "core", "0.9.6.0")
+  implementation("me.saket", "better-link-movement-method", "2.1.0")
   implementation(project(":slidingpanel"))
 
   // Quality Assurance
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
index de653dc1ebb0cc21fc8d52d49c5e5634b8f9b1b5..ee95141591bc048b821deb4e479d8e3f3de02711 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
@@ -47,12 +47,10 @@ class BufferListAdapter(
       collapsedNetworks.onNext(collapsedNetworks.value.orEmpty() + networkId)
   }
 
-  fun toggleSelection(buffer: BufferId) {
-    if (selectedBuffer.value == buffer) {
-      selectedBuffer.onNext(-1)
-    } else {
-      selectedBuffer.onNext(buffer)
-    }
+  fun toggleSelection(buffer: BufferId): Boolean {
+    val next = if (selectedBuffer.value == buffer) -1 else buffer
+    selectedBuffer.onNext(next)
+    return next != -1
   }
 
   fun unselectAll() {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
index df45c9a343af0c0faaa18557ee0e245ee3ee218b..879f3f25f91dbaaa6d4b52d2d8203abadbd5d399 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
@@ -325,6 +325,8 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     if (actionMode == null) {
       chatListToolbar.startActionMode(actionModeCallback)
     }
-    listAdapter.toggleSelection(it)
+    if (!listAdapter.toggleSelection(it)) {
+      actionMode?.finish()
+    }
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/FormattedMessage.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/FormattedMessage.kt
deleted file mode 100644
index 8b3495d704333b43504483c1f48b289309a86901..0000000000000000000000000000000000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/FormattedMessage.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package de.kuschku.quasseldroid.ui.chat.messages
-
-class FormattedMessage(
-  val id: Int,
-  val time: CharSequence,
-  val content: CharSequence,
-  val markerline: Boolean
-)
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
index 53e801e5b8d62b7e5f09531fe9e0e02d5a929a98..e1b60e6b4421a188c07a3cb241a14b6675929a49 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
@@ -2,19 +2,32 @@ package de.kuschku.quasseldroid.ui.chat.messages
 
 import android.arch.paging.PagedListAdapter
 import android.support.v7.util.DiffUtil
+import android.support.v7.widget.RecyclerView
 import android.util.LruCache
 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.protocol.Message_Flag
 import de.kuschku.libquassel.protocol.Message_Flags
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.libquassel.protocol.Message_Types
 import de.kuschku.libquassel.util.hasFlag
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.util.helper.getOrPut
+import de.kuschku.quasseldroid.util.helper.visibleIf
+import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage
+import me.saket.bettermovementmethod.BetterLinkMovementMethod
 
 class MessageAdapter(
-  private val messageRenderer: MessageRenderer
-) : PagedListAdapter<DisplayMessage, QuasselMessageViewHolder>(
+  private val messageRenderer: MessageRenderer,
+  private val clickListener: ((FormattedMessage) -> Unit)? = null,
+  private val selectionListener: ((FormattedMessage) -> Unit)? = null,
+  private val expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null
+) : PagedListAdapter<DisplayMessage, MessageAdapter.QuasselMessageViewHolder>(
   object : DiffUtil.ItemCallback<DisplayMessage>() {
     override fun areItemsTheSame(oldItem: DisplayMessage, newItem: DisplayMessage) =
       oldItem.content.messageId == newItem.content.messageId
@@ -35,7 +48,8 @@ class MessageAdapter(
         holder,
         messageCache.getOrPut(it.tag) {
           messageRenderer.render(holder.itemView.context, it)
-        }
+        },
+        it.content
       )
     }
   }
@@ -68,7 +82,10 @@ class MessageAdapter(
         messageRenderer.layout(messageType, hasHighlight),
         parent,
         false
-      )
+      ),
+      clickListener,
+      selectionListener,
+      expansionListener
     )
     messageRenderer.init(viewHolder, messageType, hasHighlight)
     return viewHolder
@@ -79,5 +96,58 @@ class MessageAdapter(
   } else {
     null
   }
+
+  class QuasselMessageViewHolder(
+    itemView: View,
+    clickListener: ((FormattedMessage) -> Unit)? = null,
+    selectionListener: ((FormattedMessage) -> Unit)? = null,
+    expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null
+  ) : RecyclerView.ViewHolder(itemView) {
+    @BindView(R.id.time)
+    lateinit var time: TextView
+
+    @BindView(R.id.content)
+    lateinit var content: TextView
+
+    @BindView(R.id.markerline)
+    lateinit var markerline: View
+
+    private var message: FormattedMessage? = null
+
+    private val localClickListener = View.OnClickListener {
+      message?.let {
+        clickListener?.invoke(it)
+      }
+    }
+
+    private val localLongClickListener = View.OnLongClickListener {
+      message?.let {
+        selectionListener?.invoke(it)
+      }
+      true
+    }
+
+    init {
+      ButterKnife.bind(this, itemView)
+
+      content.movementMethod = BetterLinkMovementMethod.getInstance()
+
+      itemView.setOnClickListener(localClickListener)
+      content.setOnClickListener(localClickListener)
+
+      itemView.setOnLongClickListener(localLongClickListener)
+      content.setOnLongClickListener(localLongClickListener)
+    }
+
+    fun bind(message: FormattedMessage) {
+      this.message = message
+
+      time.text = message.time
+      content.text = message.content
+      markerline.visibleIf(message.isMarkerLine)
+
+      this.itemView.isSelected = message.isSelected
+    }
+  }
 }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
index c7565fce76dfaaadd1577ef251b5be06e220183d..e6a55ceef714c35d98d5aa400963834639663570 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
@@ -4,13 +4,14 @@ import android.arch.lifecycle.Observer
 import android.arch.lifecycle.ViewModelProviders
 import android.arch.paging.LivePagedListBuilder
 import android.arch.paging.PagedList
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Intent
 import android.os.Bundle
 import android.support.design.widget.FloatingActionButton
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
 import butterknife.BindView
 import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.BufferId
@@ -23,6 +24,7 @@ import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.BacklogSettings
 import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
+import de.kuschku.quasseldroid.util.ui.SpanFormatter
 import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
 import javax.inject.Inject
 
@@ -53,6 +55,69 @@ class MessageListFragment : ServiceBoundFragment() {
   private var lastBuffer: BufferId? = null
   private var previousMessageId: MsgId? = null
 
+  private var actionMode: ActionMode? = null
+
+  private val actionModeCallback = object : ActionMode.Callback {
+    override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = when (item?.itemId) {
+      R.id.action_copy  -> {
+        val data = viewModel.selectedMessages.value.values.sortedBy {
+          it.id
+        }.joinToString("\n") {
+          SpanFormatter.format(
+            getString(R.string.message_format_copy),
+            it.time,
+            it.content
+          )
+        }
+
+        val clipboard = requireActivity().systemService<ClipboardManager>()
+        val clip = ClipData.newPlainText(null, data)
+        clipboard.primaryClip = clip
+        actionMode?.finish()
+        true
+      }
+      R.id.action_share -> {
+        val data = viewModel.selectedMessages.value.values.sortedBy {
+          it.id
+        }.joinToString("\n") {
+          SpanFormatter.format(
+            getString(R.string.message_format_copy),
+            it.time,
+            it.content
+          )
+        }
+
+        val intent = Intent(Intent.ACTION_SEND)
+        intent.type = "text/plain"
+        intent.putExtra(Intent.EXTRA_TEXT, data)
+        requireContext().startActivity(
+          Intent.createChooser(
+            intent,
+            requireContext().getString(R.string.label_share)
+          )
+        )
+        actionMode?.finish()
+        true
+      }
+      else              -> false
+    }
+
+    override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
+      actionMode = mode
+      mode?.menuInflater?.inflate(R.menu.context_messages, menu)
+      return true
+    }
+
+    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
+      return false
+    }
+
+    override fun onDestroyActionMode(mode: ActionMode?) {
+      actionMode = null
+      viewModel.selectedMessages.onNext(emptyMap())
+    }
+  }
+
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
@@ -75,7 +140,24 @@ class MessageListFragment : ServiceBoundFragment() {
     linearLayoutManager = LinearLayoutManager(context)
     linearLayoutManager.reverseLayout = true
 
-    adapter = MessageAdapter(messageRenderer)
+    adapter = MessageAdapter(
+      messageRenderer,
+      { msg ->
+        if (actionMode != null) {
+          if (!viewModel.selectedMessagesToggle(msg.id, msg)) {
+            actionMode?.finish()
+          }
+        }
+      },
+      { msg ->
+        if (actionMode == null) {
+          activity?.startActionMode(actionModeCallback)
+        }
+        if (!viewModel.selectedMessagesToggle(msg.id, msg)) {
+          actionMode?.finish()
+        }
+      }
+    )
     messageList.adapter = adapter
     messageList.layoutManager = linearLayoutManager
     messageList.itemAnimator = null
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt
index 65179bc540a863e27eb5acf6fbbae1e7e39b533d..5ebf18cea402a93e81b93e3bcf73fc6aff86c479 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt
@@ -3,15 +3,19 @@ package de.kuschku.quasseldroid.ui.chat.messages
 import android.content.Context
 import android.support.annotation.LayoutRes
 import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage
 
 interface MessageRenderer {
   @LayoutRes
   fun layout(type: Message_Type?, hasHighlight: Boolean): Int
 
-  fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage)
+  fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage,
+           original: QuasselDatabase.DatabaseMessage)
+
   fun render(context: Context, message: DisplayMessage): FormattedMessage
 
-  fun init(viewHolder: QuasselMessageViewHolder,
+  fun init(viewHolder: MessageAdapter.QuasselMessageViewHolder,
            messageType: Message_Type?,
            hasHighlight: Boolean) {
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
index 9716c95ab4fef30299d2c16067777974e9f635ba..5aff45b926bc88f70f1786acd95f323532df9e80 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
@@ -14,14 +14,15 @@ import de.kuschku.libquassel.protocol.Message_Flag
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.libquassel.util.hasFlag
 import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.AppearanceSettings.ColorizeNicknamesMode
 import de.kuschku.quasseldroid.settings.AppearanceSettings.ShowPrefixMode
 import de.kuschku.quasseldroid.util.helper.styledAttributes
-import de.kuschku.quasseldroid.util.helper.visibleIf
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.quassel.IrcUserUtils
 import de.kuschku.quasseldroid.util.ui.SpanFormatter
+import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage
 import org.intellij.lang.annotations.Language
 import org.threeten.bp.ZoneId
 import org.threeten.bp.format.DateTimeFormatter
@@ -61,7 +62,7 @@ class QuasselMessageRenderer @Inject constructor(
     else   -> R.layout.widget_chatmessage_placeholder
   }
 
-  override fun init(viewHolder: QuasselMessageViewHolder,
+  override fun init(viewHolder: MessageAdapter.QuasselMessageViewHolder,
                     messageType: Message_Type?,
                     hasHighlight: Boolean) {
     if (hasHighlight) {
@@ -86,11 +87,8 @@ class QuasselMessageRenderer @Inject constructor(
     viewHolder.content.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize)
   }
 
-  override fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage) {
-    holder.time.text = message.time
-    holder.content.text = message.content
-    holder.markerline.visibleIf(message.markerline)
-  }
+  override fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage,
+                    original: QuasselDatabase.DatabaseMessage) = holder.bind(message)
 
   override fun render(context: Context,
                       message: DisplayMessage): FormattedMessage {
@@ -117,7 +115,9 @@ class QuasselMessageRenderer @Inject constructor(
           formatNick(message.content.sender, self, highlight, false),
           formatContent(context, message.content.content, highlight)
         ),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Action       -> FormattedMessage(
         message.content.messageId,
@@ -128,7 +128,9 @@ class QuasselMessageRenderer @Inject constructor(
           formatNick(message.content.sender, self, highlight, false),
           formatContent(context, message.content.content, highlight)
         ),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Notice       -> FormattedMessage(
         message.content.messageId,
@@ -139,7 +141,9 @@ class QuasselMessageRenderer @Inject constructor(
           formatNick(message.content.sender, self, highlight, false),
           formatContent(context, message.content.content, highlight)
         ),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Nick         -> {
         val nickSelf = message.content.sender == message.content.content || self
@@ -161,7 +165,9 @@ class QuasselMessageRenderer @Inject constructor(
               formatNick(message.content.content, nickSelf, highlight, false)
             )
           },
-          message.isMarkerLine
+          isMarkerLine = message.isMarkerLine,
+          isExpanded = message.isExpanded,
+          isSelected = message.isSelected
         )
       }
       Message_Type.Mode         -> FormattedMessage(
@@ -173,7 +179,9 @@ class QuasselMessageRenderer @Inject constructor(
           formatPrefix(message.content.senderPrefixes, highlight),
           formatNick(message.content.sender, self, highlight, false)
         ),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Join         -> FormattedMessage(
         message.content.messageId,
@@ -184,7 +192,9 @@ class QuasselMessageRenderer @Inject constructor(
           formatNick(message.content.sender, self, highlight, true),
           message.content.content
         ),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Part         -> FormattedMessage(
         message.content.messageId,
@@ -203,7 +213,9 @@ class QuasselMessageRenderer @Inject constructor(
             formatContent(context, message.content.content, highlight)
           )
         },
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Quit         -> FormattedMessage(
         message.content.messageId,
@@ -222,7 +234,9 @@ class QuasselMessageRenderer @Inject constructor(
             formatContent(context, message.content.content, highlight)
           )
         },
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Kick         -> {
         val (user, reason) = message.content.content.split(' ', limit = 2) + listOf("", "")
@@ -245,7 +259,9 @@ class QuasselMessageRenderer @Inject constructor(
               formatContent(context, reason, highlight)
             )
           },
-          message.isMarkerLine
+          isMarkerLine = message.isMarkerLine,
+          isExpanded = message.isExpanded,
+          isSelected = message.isSelected
         )
       }
       Message_Type.Kill         -> {
@@ -269,7 +285,9 @@ class QuasselMessageRenderer @Inject constructor(
               formatContent(context, reason, highlight)
             )
           },
-          message.isMarkerLine
+          isMarkerLine = message.isMarkerLine,
+          isExpanded = message.isExpanded,
+          isSelected = message.isSelected
         )
       }
       Message_Type.NetsplitJoin -> {
@@ -282,7 +300,9 @@ class QuasselMessageRenderer @Inject constructor(
           context.resources.getQuantityString(
             R.plurals.message_netsplit_join, usersAffected, server1, server2, usersAffected
           ),
-          message.isMarkerLine
+          isMarkerLine = message.isMarkerLine,
+          isExpanded = message.isExpanded,
+          isSelected = message.isSelected
         )
       }
       Message_Type.NetsplitQuit -> {
@@ -295,7 +315,9 @@ class QuasselMessageRenderer @Inject constructor(
           context.resources.getQuantityString(
             R.plurals.message_netsplit_quit, usersAffected, server1, server2, usersAffected
           ),
-          message.isMarkerLine
+          isMarkerLine = message.isMarkerLine,
+          isExpanded = message.isExpanded,
+          isSelected = message.isSelected
         )
       }
       Message_Type.Server,
@@ -304,13 +326,17 @@ class QuasselMessageRenderer @Inject constructor(
         message.content.messageId,
         timeFormatter.format(message.content.time.atZone(zoneId)),
         formatContent(context, message.content.content, highlight),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       Message_Type.Topic        -> FormattedMessage(
         message.content.messageId,
         timeFormatter.format(message.content.time.atZone(zoneId)),
         formatContent(context, message.content.content, highlight),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
       else                      -> FormattedMessage(
         message.content.messageId,
@@ -322,7 +348,9 @@ class QuasselMessageRenderer @Inject constructor(
           formatNick(message.content.sender, self, highlight, true),
           message.content.content
         ),
-        message.isMarkerLine
+        isMarkerLine = message.isMarkerLine,
+        isExpanded = message.isExpanded,
+        isSelected = message.isSelected
       )
     }
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageViewHolder.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageViewHolder.kt
deleted file mode 100644
index ec37dd3b596c1a244ab250e56ec61231dce4e233..0000000000000000000000000000000000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageViewHolder.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package de.kuschku.quasseldroid.ui.chat.messages
-
-import android.support.v7.widget.RecyclerView
-import android.text.method.LinkMovementMethod
-import android.view.View
-import android.widget.TextView
-import butterknife.BindView
-import butterknife.ButterKnife
-import de.kuschku.quasseldroid.R
-
-class QuasselMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-  @BindView(R.id.time)
-  lateinit var time: TextView
-
-  @BindView(R.id.content)
-  lateinit var content: TextView
-
-  @BindView(R.id.markerline)
-  lateinit var markerline: View
-
-  init {
-    ButterKnife.bind(this, itemView)
-    content.movementMethod = LinkMovementMethod.getInstance()
-  }
-}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e65d8d47c4dbbcc45bc88c5b52ff608a9cc917fd
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashAdapter.kt
@@ -0,0 +1,81 @@
+package de.kuschku.quasseldroid.ui.settings.crash
+
+import android.content.Intent
+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.malheur.data.Report
+import de.kuschku.quasseldroid.R
+import org.threeten.bp.Instant
+import org.threeten.bp.ZoneId
+import org.threeten.bp.format.DateTimeFormatter
+
+class CrashAdapter : ListAdapter<Pair<Report, String>, CrashAdapter.CrashViewHolder>(
+  object : DiffUtil.ItemCallback<Pair<Report, String>>() {
+    override fun areItemsTheSame(oldItem: Pair<Report, String>?, newItem: Pair<Report, String>?) =
+      oldItem?.second == newItem?.second
+
+    override fun areContentsTheSame(oldItem: Pair<Report, String>?,
+                                    newItem: Pair<Report, String>?) =
+      oldItem == newItem
+  }
+) {
+  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = CrashViewHolder(
+    LayoutInflater.from(parent.context).inflate(R.layout.widget_crash, parent, false)
+  )
+
+  override fun onBindViewHolder(holder: CrashViewHolder, position: Int) {
+    val (report, data) = getItem(position)
+    holder.bind(report, data)
+  }
+
+  class CrashViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+    @BindView(R.id.crash_time)
+    lateinit var crashTime: TextView
+
+    @BindView(R.id.version_name)
+    lateinit var versionName: TextView
+
+    @BindView(R.id.error)
+    lateinit var error: TextView
+
+    var item: Report? = null
+    var data: String? = null
+
+    private val dateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")
+
+    init {
+      ButterKnife.bind(this, itemView)
+      itemView.setOnClickListener {
+        data?.let {
+          val intent = Intent(Intent.ACTION_SEND)
+          intent.type = "application/json"
+          intent.putExtra(Intent.EXTRA_TEXT, it)
+          itemView.context.startActivity(
+            Intent.createChooser(
+              intent,
+              itemView.context.getString(R.string.label_share_crashreport)
+            )
+          )
+        }
+      }
+    }
+
+    fun bind(item: Report, data: String) {
+      this.item = item
+      this.data = data
+
+      this.crashTime.text = item.environment?.crashTime?.let {
+        dateTimeFormatter.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault()))
+      }
+      this.versionName.text = "${item.application?.versionName}"
+      this.error.text = "${item.crash?.exception?.lines()?.firstOrNull()}"
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt
index 8b6435a233dabd84b53519b4e2c9f7cf38ffc7e9..341a591a84fd58e5813c1d6d8f0905a38f499bea 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt
@@ -1,18 +1,14 @@
 package de.kuschku.quasseldroid.ui.settings.crash
 
 import android.content.Context
-import android.content.Intent
 import android.os.Bundle
 import android.os.Handler
 import android.os.HandlerThread
 import android.support.v4.view.ViewCompat
-import android.support.v7.recyclerview.extensions.ListAdapter
-import android.support.v7.util.DiffUtil
 import android.support.v7.widget.DividerItemDecoration
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
 import android.view.*
-import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
 import com.google.gson.Gson
@@ -21,9 +17,6 @@ import de.kuschku.malheur.data.Report
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.util.helper.fromJson
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
-import org.threeten.bp.Instant
-import org.threeten.bp.ZoneId
-import org.threeten.bp.format.DateTimeFormatter
 import java.io.File
 
 class CrashSettingsFragment : ServiceBoundFragment() {
@@ -85,70 +78,6 @@ class CrashSettingsFragment : ServiceBoundFragment() {
     return view
   }
 
-  class CrashAdapter : ListAdapter<Pair<Report, String>, CrashAdapter.CrashViewHolder>(
-    object : DiffUtil.ItemCallback<Pair<Report, String>>() {
-      override fun areItemsTheSame(oldItem: Pair<Report, String>?, newItem: Pair<Report, String>?) =
-        oldItem?.second == newItem?.second
-
-      override fun areContentsTheSame(oldItem: Pair<Report, String>?,
-                                      newItem: Pair<Report, String>?) =
-        oldItem == newItem
-    }
-  ) {
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = CrashViewHolder(
-      LayoutInflater.from(parent.context).inflate(R.layout.widget_crash, parent, false)
-    )
-
-    override fun onBindViewHolder(holder: CrashViewHolder, position: Int) {
-      val (report, data) = getItem(position)
-      holder.bind(report, data)
-    }
-
-    class CrashViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-      @BindView(R.id.crash_time)
-      lateinit var crashTime: TextView
-
-      @BindView(R.id.version_name)
-      lateinit var versionName: TextView
-
-      @BindView(R.id.error)
-      lateinit var error: TextView
-
-      var item: Report? = null
-      var data: String? = null
-
-      private val dateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")
-
-      init {
-        ButterKnife.bind(this, itemView)
-        itemView.setOnClickListener {
-          data?.let {
-            val intent = Intent(Intent.ACTION_SEND)
-            intent.type = "application/json"
-            intent.putExtra(Intent.EXTRA_TEXT, it)
-            itemView.context.startActivity(
-              Intent.createChooser(
-                intent,
-                itemView.context.getString(R.string.label_share_crashreport)
-              )
-            )
-          }
-        }
-      }
-
-      fun bind(item: Report, data: String) {
-        this.item = item
-        this.data = data
-
-        this.crashTime.text = item.environment?.crashTime?.let {
-          dateTimeFormatter.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault()))
-        }
-        this.versionName.text = "${item.application?.versionName}"
-        this.error.text = "${item.crash?.exception?.lines()?.firstOrNull()}"
-      }
-    }
-  }
-
   override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
     inflater?.inflate(R.menu.activity_crashes, menu)
     super.onCreateOptionsMenu(menu, inflater)
diff --git a/app/src/main/res/layout/widget_chatmessage_action.xml b/app/src/main/res/layout/widget_chatmessage_action.xml
index 7006f8f56e6968aeb9f06f92a2a5a4aeffbb5f2f..3cb90f5c9d8ccf8147788d8186cad22dd972daae 100644
--- a/app/src/main/res/layout/widget_chatmessage_action.xml
+++ b/app/src/main/res/layout/widget_chatmessage_action.xml
@@ -3,6 +3,7 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
+  android:background="?attr/backgroundMenuItem"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
@@ -33,7 +34,6 @@
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:textColor="?attr/colorForegroundAction"
-      android:textIsSelectable="true"
       android:textStyle="italic"
       tools:text="@sample/messages.json/data/message" />
   </LinearLayout>
diff --git a/app/src/main/res/layout/widget_chatmessage_error.xml b/app/src/main/res/layout/widget_chatmessage_error.xml
index 462ec5cc8d930438ac90dd3d88c144bcf0e77d7b..d1f1e2ec32ba30ec8ea4b728d92fc8a45ecbc380 100644
--- a/app/src/main/res/layout/widget_chatmessage_error.xml
+++ b/app/src/main/res/layout/widget_chatmessage_error.xml
@@ -3,6 +3,7 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
+  android:background="?attr/backgroundMenuItem"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
@@ -33,7 +34,6 @@
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:textColor="?attr/colorForegroundError"
-      android:textIsSelectable="true"
       tools:text="@sample/messages.json/data/message" />
   </LinearLayout>
 
diff --git a/app/src/main/res/layout/widget_chatmessage_info.xml b/app/src/main/res/layout/widget_chatmessage_info.xml
index 87de55c2f0bf7080febacd656f2a06997a9d27cd..42182a387149d51ec26adf47923771e1a9f0c315 100644
--- a/app/src/main/res/layout/widget_chatmessage_info.xml
+++ b/app/src/main/res/layout/widget_chatmessage_info.xml
@@ -3,6 +3,7 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
+  android:background="?attr/backgroundMenuItem"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
@@ -33,7 +34,6 @@
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:textColor="?attr/colorForegroundSecondary"
-      android:textIsSelectable="true"
       android:textStyle="italic"
       tools:text="@sample/messages.json/data/message" />
   </LinearLayout>
diff --git a/app/src/main/res/layout/widget_chatmessage_notice.xml b/app/src/main/res/layout/widget_chatmessage_notice.xml
index f17215680930826b6b2bd97dfb4476ea7cacfe2e..87979a516fe0c2d64afa88abe2a172f02ff820c7 100644
--- a/app/src/main/res/layout/widget_chatmessage_notice.xml
+++ b/app/src/main/res/layout/widget_chatmessage_notice.xml
@@ -3,6 +3,7 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
+  android:background="?attr/backgroundMenuItem"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
@@ -33,7 +34,6 @@
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:textColor="?attr/colorForegroundNotice"
-      android:textIsSelectable="true"
       tools:text="@sample/messages.json/data/message" />
   </LinearLayout>
 
diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml
index 4b321468116795fb942f2a004ef024ca2ccca5d9..451a8535e3407b8fddd2ff6582743f7c89fdb107 100644
--- a/app/src/main/res/layout/widget_chatmessage_plain.xml
+++ b/app/src/main/res/layout/widget_chatmessage_plain.xml
@@ -3,6 +3,7 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
+  android:background="?attr/backgroundMenuItem"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
@@ -33,7 +34,6 @@
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:textColor="?attr/colorForeground"
-      android:textIsSelectable="true"
       tools:text="@sample/messages.json/data/message" />
   </LinearLayout>
 
diff --git a/app/src/main/res/layout/widget_chatmessage_server.xml b/app/src/main/res/layout/widget_chatmessage_server.xml
index 3a63850b6c5c3184506234cef6f8f16f7fcecf55..7ab995cb83252a7786460729ad325ae8c9d91b5e 100644
--- a/app/src/main/res/layout/widget_chatmessage_server.xml
+++ b/app/src/main/res/layout/widget_chatmessage_server.xml
@@ -3,6 +3,7 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
+  android:background="?attr/backgroundMenuItem"
   android:orientation="vertical"
   android:textAppearance="?android:attr/textAppearanceListItemSmall">
 
@@ -33,7 +34,6 @@
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:textColor="?attr/colorForegroundSecondary"
-      android:textIsSelectable="true"
       tools:text="@sample/messages.json/data/message" />
   </LinearLayout>
 
diff --git a/app/src/main/res/menu/context_messages.xml b/app/src/main/res/menu/context_messages.xml
new file mode 100644
index 0000000000000000000000000000000000000000..937ca54b1aa02d2e2318cc3fe43a94d40ae5479a
--- /dev/null
+++ b/app/src/main/res/menu/context_messages.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+  <item
+    android:id="@+id/action_copy"
+    android:title="@string/label_copy" />
+  <item
+    android:id="@+id/action_share"
+    android:title="@string/label_share" />
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 921550714cc807620a4dbf07df17f4957192fd1f..05a6f9bc48bab60f69b66da1f26344671dc6ad8e 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -13,6 +13,7 @@
   <string name="label_close">Schließen</string>
   <string name="label_connect">Verbinden</string>
   <string name="label_contributors">Mitwirkende</string>
+  <string name="label_copy">Kopieren</string>
   <string name="label_crashes">Absturzberichte</string>
   <string name="label_delete">Löschen</string>
   <string name="label_delete_all">Alle Löschen</string>
@@ -33,6 +34,7 @@
   <string name="label_save">Speichern</string>
   <string name="label_select_multiple">Auswählen</string>
   <string name="label_settings">Einstellungen</string>
+  <string name="label_share">Teilen</string>
   <string name="label_share_crashreport">Absturzbericht Teilen</string>
   <string name="label_show_hidden">Alle anzeigen</string>
   <string name="label_unhide">Nicht mehr ausblenden</string>
diff --git a/app/src/main/res/values-de/strings_messages.xml b/app/src/main/res/values-de/strings_messages.xml
index 1d781ce461e3913587da2e154abe1102f8b3e364..aa54ae050c94fb9fdb664f4a36198563c7676cc5 100644
--- a/app/src/main/res/values-de/strings_messages.xml
+++ b/app/src/main/res/values-de/strings_messages.xml
@@ -6,6 +6,9 @@
   <string name="message_type_nick">Benutzernamensänderungen</string>
   <string name="message_type_mode">Modiänderungen</string>
   <string name="message_type_topic">Themenänderungen</string>
+
+  <string name="message_format_copy">[%1$s] %2$s</string>
+
   <string name="message_format_plain">%1$s%2$s: %3$s</string>
   <string name="message_format_action">* %1$s%2$s %3$s</string>
   <string name="message_format_notice">[%1$s%2$s] %3$s</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ff29dc0a000fb450af3b1338110f42b11d9777ad..a25b5e0715b3aba4e2bb2a1a9bda72fe089eabe2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,6 +13,7 @@
   <string name="label_close">Close</string>
   <string name="label_connect">Connect</string>
   <string name="label_contributors">Contributors</string>
+  <string name="label_copy">Copy</string>
   <string name="label_crashes">Crashes</string>
   <string name="label_delete">Delete</string>
   <string name="label_delete_all">Delete All</string>
@@ -33,6 +34,7 @@
   <string name="label_save">Save</string>
   <string name="label_select_multiple">Select</string>
   <string name="label_settings">Settings</string>
+  <string name="label_share">Share</string>
   <string name="label_share_crashreport">Share Crashreport</string>
   <string name="label_show_hidden">Show Hidden</string>
   <string name="label_unhide">Make Visible</string>
diff --git a/app/src/main/res/values/strings_messages.xml b/app/src/main/res/values/strings_messages.xml
index 802cb0a7156cd59954798ffb8535d9a6665c1ac6..e32ff0ed4d93480ef1692cd205dbe83e4866e6fa 100644
--- a/app/src/main/res/values/strings_messages.xml
+++ b/app/src/main/res/values/strings_messages.xml
@@ -15,6 +15,8 @@
     <item>@string/message_type_topic</item>
   </string-array>
 
+  <string name="message_format_copy">[%1$s] %2$s</string>
+
   <string name="message_format_plain">%1$s%2$s: %3$s</string>
   <string name="message_format_action">* %1$s%2$s %3$s</string>
   <string name="message_format_notice">[%1$s%2$s] %3$s</string>
diff --git a/viewmodel/build.gradle.kts b/viewmodel/build.gradle.kts
index 28548b70aa1e2433a102cac5d4e339714f67fbc1..9f832911d0f4e1e87659e086b2e03d60c7aa5eb2 100644
--- a/viewmodel/build.gradle.kts
+++ b/viewmodel/build.gradle.kts
@@ -37,6 +37,7 @@ dependencies {
   implementation("org.jetbrains", "annotations", "16.0.1")
 
   // Quassel
+  implementation(project(":persistence"))
   implementation(project(":lib")) {
     exclude(group = "org.threeten", module = "threetenbp")
   }
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
index f7088caf186775f06ccad71fc50450e21ba439a7..0667ecb3c9bb357793a85c334c9853994a4a28b2 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
@@ -28,7 +28,14 @@ import java.util.concurrent.TimeUnit
 class QuasselViewModel : ViewModel() {
   val backendWrapper = BehaviorSubject.createDefault(Observable.empty<Optional<Backend>>())
 
-  val selectedMessages = BehaviorSubject.createDefault(emptyList<MsgId>())
+  val selectedMessages = BehaviorSubject.createDefault(emptyMap<MsgId, FormattedMessage>())
+  fun selectedMessagesToggle(key: MsgId, value: FormattedMessage): Boolean {
+    val set = selectedMessages.value.orEmpty()
+    val result = if (set.containsKey(key)) set - key else set + Pair(key, value)
+    selectedMessages.onNext(result)
+    return result.isNotEmpty()
+  }
+
   val expandedMessages = BehaviorSubject.createDefault(emptyList<MsgId>())
 
   val buffer = BehaviorSubject.createDefault(-1)
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt
new file mode 100644
index 0000000000000000000000000000000000000000..27f674a99e2a3010de2a15cbe45d5393a2444adc
--- /dev/null
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt
@@ -0,0 +1,10 @@
+package de.kuschku.quasseldroid.viewmodel.data
+
+class FormattedMessage(
+  val id: Int,
+  val time: CharSequence,
+  val content: CharSequence,
+  val isSelected: Boolean,
+  val isExpanded: Boolean,
+  val isMarkerLine: Boolean
+)
\ No newline at end of file