How to get list of templates for the active theme?
This is a working prototype, although I will be rewritting it a bit later.

&lt;?php

/**
 * @file
 * Contains \Drupal\mymodule\Form\ThemeTemplateForm.
 */

namespace Drupal\mymodule\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Theme\Registry;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Component\Utility\String;


/**
 * Form for editing template files for current theme.
 */
class ThemeTemplateForm extends FormBase {

  /**
   * The cache backend to use for the complete theme registry data.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cache;

  /**
   * The lock backend that should be used.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface
   */
  protected $lock;

  /**
   * The module handler to use to load modules.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The theme registry service.
   *
   * @var \Drupal\Core\Theme\Registry
   */
  protected $registry;

  /**
   * The template that is being edited/created.
   *
   * @var string
   */
  protected $template;

  /**
   * The front-end theme that we'll be editing templates for.
   *
   * @var string
   */
  protected $theme;

  /**
   * Creates ThemeTemplateForm object.
   *
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   The cache backend interface to use for the complete theme registry data.
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
   *   The lock backend.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler to use to load modules.
   */
  public function __construct(CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler) {
    $this-&gt;cache = $cache;
    $this-&gt;lock = $lock;
    $this-&gt;moduleHandler = $module_handler;
    $this-&gt;theme = $this-&gt;config('system.theme')-&gt;get('default');
    $this-&gt;registry = new Registry($this-&gt;cache, $this-&gt;lock, $this-&gt;moduleHandler, $this-&gt;theme);

    $template = $this-&gt;getRequest()-&gt;query-&gt;get('template');
    if (!empty($template) &amp;&amp; $this-&gt;templateIsAllowed($template)) {
      // Always use machine name of the template
      $this-&gt;template = str_replace('-', '_', $template);
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container-&gt;get('cache.default'),
      $container-&gt;get('lock'),
      $container-&gt;get('module_handler')
    );
  }

  /**
   * Returns path to selected theme.
   *
   * @return string
   *   Path relative to Drupal root.
   */
  public function themePath() {
    return drupal_get_path('theme', $this-&gt;theme);
  }


  /**
   * Returns the list of template files.
   */
  public function templateList() {
    $list = array_filter($this-&gt;registry-&gt;get(), function($info) {
      return isset($info['template']);
    });
    // Allow modules to alter the list
    \Drupal::moduleHandler()-&gt;alter('editable_template_list', $list);
    ksort($list);
    return $list;
  }

  /**
   * Returns list of overridden templates by the front-end theme.
   */
  public function overriddenTemplateList() {
    $list = array_filter($this-&gt;templateList(), function($info) {
      // The system will return templates that have preprocess functions
      // implemented by the theme so we need to remove them from the list
      // since they aren't really overridden.
      $theme_path = $this-&gt;themePath();
      return $info['theme path'] == $theme_path &amp;&amp; (isset($info['path']) &amp;&amp; stripos($info['path'], $theme_path) === 0);
    });
    return $list;
  }

  /**
   * See if provided template is allowed.
   *
   * @param string $template
   *   Template name.
   *
   * @return bool
   *   TRUE if template is allowed, FALSE otherwise.
   */
  public function templateIsAllowed($template) {
    $list = $this-&gt;templateList();
    $list[$template] = array();
    \Drupal::moduleHandler()-&gt;alter('editable_template_list', $list);
    // If no module removed the template the template is allowed
    return isset($list[$template]);
  }

  /**
   * Returns information if the provided template is overridden by the theme.
   *
   * @param string $template
   *   The template name.
   *
   * @return bool
   *   Returns TRUE if the template is overridden, FALSE otherwise.
   */
  public function templateIsOverridden($template) {
    $overridden = $this-&gt;overriddenTemplateList();
    return isset($overridden[$template]);
  }

  /**
   * Returns the template information.
   *
   * @param string $template
   *   The template name.
   *
   * @return array
   *   Returns the template information array. Empty array is returned
   *   when template was not found.
   */
  public function templateInfo($template) {
    $templates = $this-&gt;templateList();
    if (isset($templates[$template])) {
      return $templates[$template];
    } else {
      $base_hook = $this-&gt;registry-&gt;getBaseHook($template);
      return $base_hook ? $template[$base_hook] : array();
    }
  }

  /**
   * Returns the source path of the template file.
   *
   * @param string $template
   *   Template name.
   *
   * @return string
   *   Path to the source template file relative to Drupal root.
   */
  public function getTemplateSourcePath($template) {
    $info = $this-&gt;templateInfo($template);
    return $info['template'] . '.html.twig';
  }

  /**
   * Returns the path of the template in the theme.
   * If the template does not exist(is not overridden yet)
   * the 'templates' directory within the theme directory will
   * be used as destination for the new template. Otherwise
   * 'path' will be used since themes don't have to explicitly put
   * their templates into the 'templates'directory.
   *
   * @param string $template
   *   Template name.
   *
   * @return string
   *   The path to the template file within the theme directory
   *   relative to Drupal root.
   */
  public function getTemplateThemePath($template) {
    $info = $this-&gt;templateInfo($template);
    if (isset($info['path']) &amp;&amp; stripos($info['path'], $this-&gt;themePath()) === 0) {
      return $info['path'] . '/' . str_replace('_', '-', $template) . '.html.twig';
    } else{
      return $this-&gt;themePath() . '/templates/' . str_replace('_', '-', $template) . '.html.twig';
    }
  }

  /**
   * See if provided path is allowed for writing/overriding
   * a template file.
   *
   * @param string $path
   *   Path to the template relative to Drupal root.
   *
   * @return bool
   *   TRUE if template can be created/overridden, FALSE otherwise.
   */
  public function pathIsAllowed($path) {
    // Access is allowed only to theme's directory AND this
    // always has to be located within the 'sites' directory.
    return stripos($path, $this-&gt;themePath()) === 0 &amp;&amp; stripos($path, 'sites/') === 0;
  }

  /**
   * Returns content of requested template.
   * If the template is not implemented by the theme
   * the content of the original template will be returned.
   *
   * @param string $template
   *   Template name.
   * @return string|false
   *   Returns the code of the template or FALSE if template could not be loaded.
   */
  public function getTemplateCode($template) {
    $info = $this-&gt;templateInfo($template);
    if (empty($info)) {
      return FALSE;
    }
    $path = isset($info['path']) ? $info['path'] . '/' . $template : $info['template'];
    $path .= '.html.twig';
    return @file_get_contents('.' . base_path() . '/' . $path);
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'theme_template_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, array &amp;$form_state) {
    $templates = $this-&gt;templateList();
    $overridden = $this-&gt;overriddenTemplateList();

    if ($this-&gt;template) {
      $form['#title'] = $this-&gt;t('Template: @template', array('@template' =&gt; str_replace('_', '-', $this-&gt;template)));
    } else {
      $form['#title'] = $this-&gt;t('Create new template');
    }

    $form['left'] = array(
      '#type' =&gt; 'container',
      '#attributes' =&gt; array(
        'id' =&gt; 'template-edit-left'
      )
    );

    $form['left']['edit'] = array(
      '#type' =&gt; 'textarea',
      '#rows' =&gt; 35,
      '#default_value' =&gt; $this-&gt;template ? $this-&gt;getTemplateCode($this-&gt;template) : NULL,
      '#required' =&gt; TRUE
    );

    $form['left']['name_wrapper'] = array(
      '#type' =&gt; 'fieldset',
      '#title' =&gt; $this-&gt;t('Template name'),
      '#collapsible' =&gt; TRUE,
      '#collapsed' =&gt; TRUE
    );
    $form['left']['name_wrapper']['name'] = array(
      '#type' =&gt; 'textfield',
      '#title' =&gt; $this-&gt;t('Template name'),
      '#required' =&gt; !$this-&gt;template,
      '#field_prefix' =&gt; $this-&gt;template ? String::checkPlain(str_replace('_', '-', $this-&gt;template)) . '--' : NULL
    );

    $form['left']['actions'] = array(
      '#type' =&gt; 'actions',
      'save' =&gt; array(
        '#type' =&gt; 'submit',
        '#name' =&gt; 'save',
        '#value' =&gt; $this-&gt;t('Save')
      ),
      'delete' =&gt; array(
        '#type' =&gt; 'submit',
        '#name' =&gt; 'delete',
        '#value' =&gt; $this-&gt;t('Delete'),
        '#access' =&gt; $this-&gt;template &amp;&amp; $this-&gt;templateIsOverridden($this-&gt;template)
      )
    );

    $form['right'] = array(
      '#type' =&gt; 'container',
      '#attributes' =&gt; array(
        'id' =&gt; 'template-edit-right'
      ),
      'templates' =&gt; array(
        '#theme' =&gt; 'item_list',
        '#items' =&gt; array()
      )
    );
    foreach ($overridden AS $template =&gt; $info) {
      $form['right']['templates']['#items'][$template] = array(
        '#type' =&gt; 'link',
        '#title' =&gt; str_replace('_', '-', $template),
        '#route_name' =&gt; 'mymodule.settings.theme.template',
        '#options' =&gt; array(
          'query' =&gt; array(
            'template' =&gt; str_replace('_', '-', $template)
          )
        ),
        '#attributes' =&gt; array(
          'class' =&gt; array('overridden')
        )
      );
    }
    foreach ($templates AS $template =&gt; $info) {
      if (isset($form['right']['templates']['#items'][$template])) {
        continue;
      }
      $form['right']['templates']['#items'][$template] = array(
        '#type' =&gt; 'link',
        '#title' =&gt; str_replace('_', '-', $template),
        '#route_name' =&gt; 'mymodule.settings.theme.template',
        '#options' =&gt; array(
          'query' =&gt; array(
            'template' =&gt; str_replace('_', '-', $template)
          )
        )
      );
    }

    if ($this-&gt;template &amp;&amp; isset($form['right']['templates']['#items'][$this-&gt;template])) {
      $form['right']['templates']['#items'][$this-&gt;template]['#attributes']['class'][] = 'active';
    }

    $form['#attached'] = array(
      'css' =&gt; array(
        drupal_get_path('module', 'mymodule') . '/css/mymodule.themetemplateform.css.less'
      ),
      'library' =&gt; array(
        'mymodule/codemirror.twig'
      ),
      'js' =&gt; array(
        drupal_get_path('module', 'mymodule') . '/js/mymodule.themetemplateform.js'
      )
    );

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &amp;$form, array &amp;$form_state) {
    // If there is no template, the 'name' is required so we'll always end up with a value
    // for the name.
    $template = $this-&gt;template ? $this-&gt;template : '';
    if ((string) $form_state['values']['name'] !== '') {
      $template = (string) $template === '' ? $form_state['values']['name'] : $template . '__' . $form_state['values']['name'];
    }
    $template = str_replace('-', '_', $template);
    if (!$this-&gt;templateIsAllowed($template)) {
      $this-&gt;setFormError('name', $form_state, $this-&gt;t("Template name is not allowed."));
    } elseif (!$this-&gt;pathIsAllowed($this-&gt;getTemplateThemePath($template))) {
      $this-&gt;setFormError('name', $form_state, $this-&gt;t("Template cannot be changed."));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &amp;$form, array &amp;$form_state) {
    // If there is no template, the 'name' is required so we'll always end up with a value
    // for the name.
    $template = $this-&gt;template ? $this-&gt;template : '';
    if ((string) $form_state['values']['name'] !== '') {
      $template = (string) $template === '' ? $form_state['values']['name'] : $template . '__' . $form_state['values']['name'];
    }
    $template = str_replace('-', '_', $template);

    $tplpath = $this-&gt;getTemplateThemePath($template);

    switch ($form_state['triggering_element']['#name']) {
      case 'save':
        $write = @file_put_contents('.' . base_path() . $tplpath, $form_state['values']['edit']);
        if ($write === FALSE) {
          $this-&gt;setFormError('edit', $form_state, $this-&gt;t("Couldn't write data into file. Please try again or contact help desk."));
        } else {
          $form_state['redirect_route'] = array(
            'route_name' =&gt; 'mymodule.settings.theme.template',
            'options' =&gt; array(
              'query' =&gt; array(
                'template' =&gt; str_replace('_', '-', $template)
              )
            )
          );
          mymodule_success_message();
        }
        break;

      case 'delete':
        $delete = @unlink('.' . base_path() . $tplpath);
        if ($delete) {
          $form_state['redirect_route'] = array(
            'route_name' =&gt; 'mymodule.settings.theme.template'
          );
          mymodule_success_message();
        } else {
          $this-&gt;setFormError('edit', $form_state, $this-&gt;t("Couldn't delete template. File does not exist or is not accessible."));
        }
        break;
    }

    // Always rebuild cache
    Cache::invalidateTags(array('theme_registry' =&gt; TRUE));
  }

}