// -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode:
// nil -*-
//
//  Copyright (C) 2006-2015 Andrej Vodopivec <andrej.vodopivec@gmail.com>
//            (C) 2012 Doug Ilijev <doug.ilijev@gmail.com>
//            (C) 2014-2020 Gunter Königsmann <wxMaxima@physikbuch.de>
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//  SPDX-License-Identifier: GPL-2.0+

/*! \file
  This file defines the class EditorCell

  EditorCell is the Cell type that represents the field that contains user
  input.
*/

#include "EditorCell.h"

#include "Bidi.h"
#include "CellImpl.h"
#include "CellPointers.h"
#include "MarkDown.h"
#include "wxMaxima.h"
#include "wxMaximaFrame.h"
#include <algorithm>
#include <wx/clipbrd.h>
#include <wx/regex.h>
#include <wx/tokenzr.h>
#include "RegexSearch.h"

EditorCell::EditorCell(GroupCell *group, Configuration *config,
                       wxString text)
  : Cell(group, config), m_text(text) {
  // Do NOT translate '\r' to '\n' here: '\r' is wxMaxima's *soft* (word-wrap)
  // line break and '\n' is a *hard* one. Converting them freezes a transient
  // layout decision into permanent content -- e.g. when a cell is copied (the
  // copy ctor delegates here) after having been word-wrapped at a tiny/zero
  // width, every soft break became a hard one and the text stayed broken after
  // every word regardless of the real width. The Unicode line/paragraph
  // separators below are genuine hard breaks, so those do map to '\n'.
  text.Replace(wxS("\u2028"), "\n");
  text.Replace(wxS("\u2029"), "\n");
  if(m_configuration->GetMatchParens())
    {
      if(text == wxS("("))
        {
          SetValue(wxS("()"));
          CursorPosition(1);
        }
      else if(text == wxS("["))
        {
          SetValue(wxS("[]"));
          CursorPosition(1);
        }
      else if(text == wxS("{"))
        {
          SetValue(wxS("{}"));
          CursorPosition(1);
        }
      else SetValue(NormalizeLineEndings(text));
    }
  else SetValue(NormalizeLineEndings(text));
  m_height = m_charHeight + 2 * Scale_Px(2);
  m_center = m_height / 2;
  m_width = 2 * Scale_Px(2);
  SetStyle(TS_CODE_DEFAULT);
}

wxString EditorCell::EscapeHTMLChars(wxString input) {
  input.Replace(wxS("&"), wxS("&amp;"));
  input.Replace(wxS("\""), wxS("&quot;"));
  input.Replace(wxS("<"), wxS("&lt;"));
  input.Replace(wxS(">"), wxS("&gt;"));
  input.Replace(wxS("\n"), wxS("<br>\n"));
  input.Replace(wxS("\r"), wxS(" "));
  return input;
}

DEFINE_CELL(EditorCell)

// --- EditorCell split ----------------------------------------------------
// Two subclasses along the only axis that actually diverges: code vs. prose.
// CodeEditorCell is Maxima input (and the editor Maxima's questions are
// answered in - those expect Maxima input too); TextEditorCell is text and all
// sectioning levels (which differ only in font size). For now they add almost
// little behavior yet (the IsCodeEditor() discriminator and the StyleTypedText()
// code-vs-prose styling seam): most logic still lives in EditorCell keyed on
// m_type. Construction is funnelled through
// EditorCell::Create so every EditorCell is born as the right subtype. A cell's
// type never flips in place across the code/text boundary: the "Convert to ..."
// commands go through Worksheet::SetCellStyle, which builds a fresh GroupCell of
// the target type (hence the right subclass here) and records undo. So the
// instance's subclass always matches its role, ready for behavior to move into
// overrides. They live here rather than in the header because nothing outside
// this file needs to name them yet; they only reach the world via Create()/Copy().
class CodeEditorCell final : public EditorCell {
public:
  CodeEditorCell(GroupCell *group, Configuration *config, wxString text = {})
    : EditorCell(group, config, std::move(text)) {}
  CodeEditorCell(GroupCell *group, const CodeEditorCell &cell)
    : EditorCell(group, cell) {}
  std::unique_ptr<Cell> Copy(GroupCell *group) const override;
  bool IsCodeEditor() const override { return true; }
  //! Code cells get Maxima syntax styling instead of the prose default.
  void StyleTypedText() const override { StyleTextCode(); }
  //! Code input auto-closes a just-typed bracket/quote and %-prefixes a leading
  //! operator; prose does neither.
  wxString PreprocessNewValue(const wxString &text,
                              std::size_t &cursorPos) const override;
  //! Code cells always serialize as type="input".
  wxString XMLTypeAttribute() const override { return wxS(" type=\"input\""); }
  //! Code serializes to RTF snippet-by-snippet, keeping the syntax
  //! highlighting; prose maps its heading/text style in the base.
  wxString ToRTF() const override;
};

class TextEditorCell final : public EditorCell {
public:
  TextEditorCell(GroupCell *group, Configuration *config, wxString text = {})
    : EditorCell(group, config, std::move(text)) {}
  TextEditorCell(GroupCell *group, const TextEditorCell &cell)
    : EditorCell(group, cell) {}
  std::unique_ptr<Cell> Copy(GroupCell *group) const override;
};

DEFINE_CELL_COPY(CodeEditorCell)
DEFINE_CELL_COPY(TextEditorCell)

wxString CodeEditorCell::PreprocessNewValue(const wxString &text,
                                            std::size_t &cursorPos) const {
  wxString result = text;
  if (m_configuration->GetMatchParens()) {
    // A just-typed opening bracket/quote is auto-closed, with the caret placed
    // inside it.
    if (text == wxS("(")) {
      result = wxS("()");
      cursorPos = 1;
    } else if (text == wxS("[")) {
      result = wxS("[]");
      cursorPos = 1;
    } else if (text == wxS("{")) {
      result = wxS("{}");
      cursorPos = 1;
    } else if (text == wxS("\"")) {
      result = wxS("\"\"");
      cursorPos = 1;
    } else {
      cursorPos = result.Length();
    }
  } else {
    cursorPos = result.Length();
  }

  // A cell that starts with a binary operator is a continuation of the previous
  // result: prefix "%" so e.g. "*2" becomes "%*2".
  if (m_configuration->GetInsertAns()) {
    if (result == wxS("+") || result == wxS("*") || result == wxS("/") ||
        result == wxS("^") || result == wxS("=") || result == wxS(",")) {
      result = wxS("%") + result;
      cursorPos = result.Length();
    }
  }
  return result;
}

bool EditorCell::IsCodeType(CellType type) {
  return (type == MC_TYPE_INPUT) || (type == MC_TYPE_PROMPT);
}

std::unique_ptr<EditorCell> EditorCell::Create(GroupCell *group,
                                               Configuration *config,
                                               CellType type, wxString text) {
  if (IsCodeType(type))
    return std::make_unique<CodeEditorCell>(group, config, std::move(text));
  return std::make_unique<TextEditorCell>(group, config, std::move(text));
}

void EditorCell::AddDrawParameter(wxString param) {
  SaveValue();

  if (param == wxEmptyString)
    return;

  wxString paramTrimmed = param;
  paramTrimmed.Trim();
  if (paramTrimmed == wxEmptyString)
    return;

  size_t pos = 1;

  // Insert a comma in front of the parameter, if necessary
  wxString::const_iterator ch = m_text.begin();
  bool commaNeededBefore = false;
  bool commaNeededAfter = false;
  while (ch < m_text.end()) {
    if ((*ch == wxS('(')) || (*ch == wxS('[')) || (*ch == wxS(',')))
      commaNeededBefore = false;
    else {
      if (!((*ch == wxS(' ')) || (*ch == wxS('\n')) || (*ch == wxS('\r')) ||
            (*ch == wxS('\t'))))
        commaNeededBefore = true;
    }

    if (pos > CursorPosition())
      break;
    else {
      ++ch;
      ++pos;
    }
  }

  // if(ch < m_text.end())
  //  ++ch;

  while (ch < m_text.end()) {
    if ((*ch == wxS(')')) || (*ch == wxS(']')) || (*ch == wxS(','))) {
      commaNeededAfter = false;
      break;
    }
    if ((*ch != wxS(' ')) && (*ch != wxS('\n')) && (*ch != wxS('\r')) &&
        (*ch != wxS('\t'))) {
      commaNeededAfter = true;
      break;
    }

    ++ch;
    ++pos;
  }

  if (commaNeededAfter)
    param += wxS(",");

  wxString textAfterParameter =
    m_text.Right(m_text.Length() - CursorPosition());
  m_text = m_text.Left(CursorPosition());
  m_text.Trim();
  if (commaNeededBefore) {
    m_text += wxS(",");
    CursorMove(1);
  }

  wxStringTokenizer lines(param, wxS("\n"), wxTOKEN_RET_EMPTY_ALL);
  while (lines.HasMoreTokens()) {
    // Todo: Don't insert a newline if we are at the beginning of a line.
    ProcessNewline(false);
    wxString line = lines.GetNextToken();
    line.Trim(false);
    m_text += line;
    CursorMove(line.Length());
  }
  m_text += textAfterParameter;
  StyleText();
  ContainsChanges(true);
}

void EditorCell::SearchStartedHere(size_t index) const {
  m_viewCellPointers->SetSearchStart(const_cast<EditorCell *>(this), index);
}

void EditorCell::SearchStartedHere() const {
  m_viewCellPointers->SetSearchStart(const_cast<EditorCell *>(this),
                                 CursorPosition());
}

void EditorCell::MouseSelectionStartedHere() const {
  m_viewCellPointers->SetMouseSelectionStart(const_cast<EditorCell *>(this));
}

void EditorCell::KeyboardSelectionStartedHere() const {
  m_viewCellPointers->SetKeyboardSelectionStart(const_cast<EditorCell *>(this));
}

wxString EditorCell::GetFullCommandUnderCursor() {
  if (!IsActive())
    return wxEmptyString;

  if (m_text == wxEmptyString)
    return wxEmptyString;

  wxString result;
  size_t pos = 1;

  wxString::const_iterator ch = m_text.begin();
  while (ch < m_text.end()) {
    result += *ch;
    if (*ch == wxS('\\')) {
      ++ch;
      ++pos;
      if (ch < m_text.end())
        result += *ch;
    } else {
      if ((*ch == ';') || (*ch == '$')) {
        if (CursorPosition() < pos)
          return result;
        result.Clear();
      }
    }

    if (ch < m_text.end()) {
      ++ch;
      ++pos;
    }
  }
  return result;
}

wxString EditorCell::PrependNBSP(wxString input) {
  bool firstSpace = true;
  wxString retval;

  input.Replace(wxS("\r"), wxS(" "));

  for (const auto &ch : input) {
    if (ch == wxS('\n'))
      firstSpace = true;

    if (ch == wxS(' ')) {
      if (firstSpace) {
        firstSpace = false;
        retval += ch;
      } else
        retval += wxS("\u00A0");
    } else {
      retval += ch;
      firstSpace = true;
    }
  }
  return retval;
}

EditorCell::EditorCell(GroupCell *group, const EditorCell &cell)
  : EditorCell(group, cell.m_configuration, cell.m_text) {
  CopyCommonData(cell);
  m_history = cell.m_history;
}

wxString EditorCell::ToString() const { return ToString(false); }

wxString EditorCell::ToString(bool dontLimitToSelection) const {
  // Soft breaks live in a side table, never in m_text, so the content needs no
  // scrubbing before it is handed out.
  wxString text = m_text;
  // Convert non-breakable spaces to breakable ones
  text.Replace(wxS("\u00a0"), wxS(" "));

  if (SelectionActive() && (!dontLimitToSelection)) {
    auto start = SelectionLeft();
    auto end   = SelectionRight();
    if (start >= m_text.Length())
      {
        if(m_text.Length() > 0)
          start = m_text.Length() - 1;
        else
          start = 0;
      }
    if(end > 0)
      end--;
    text = m_text.SubString(start, end);
  }
  return text;
}

wxString EditorCell::ToMatlab() const { return ToMatlab(false); }

wxString EditorCell::ToMatlab(bool dontLimitToSelection) const {
  wxString text = m_text;
  // Convert non-breakable spaces to breakable ones
  text.Replace(wxS("\u00a0"), wxS(" "));

  if (SelectionActive() && (!dontLimitToSelection)) {
    auto start = SelectionLeft();
    auto end   = SelectionRight();
    if (start >= m_text.Length())
      {
        if(m_text.Length() > 0)
          start = m_text.Length() - 1;
        else
          start = 0;
      }
    if(end > 0)
      end--;
    text = m_text.SubString(start, end);
  }
  return text;
}

wxString CodeEditorCell::ToRTF() const {
  // The (dead in practice) MC_TYPE_PROMPT case: prompts are code editors by
  // construction (see EditorCell::IsCodeType), but the visible (%i1) labels
  // are TextCells, so an EditorCell of this type shouldn't occur.
  if (m_type == MC_TYPE_PROMPT)
    return wxString::Format(wxS("\\cf%li"), static_cast<long>(GetTextStyle())) +
      wxS("\\pard\\s22\\li1105\\lin1105\\fi-1105\\f0\\fs24 ") +
      RTFescape(GetValue()) + wxS("\n");

  wxString retval(wxS(" "));
  for (const auto &textSnippet : m_styledText) {
    wxString escaped = RTFescape(textSnippet.GetText());

    if (textSnippet.IsStyleSet()) {
      retval +=
        wxString::Format(wxS("\\cf%li "), static_cast<long>(textSnippet.GetTextStyle()));
      retval += escaped;
    } else {
      retval += wxString::Format(wxS("\\cf%li "), static_cast<long>(TS_CODE_DEFAULT));
      retval += wxS("{") + escaped + wxS("}\n");
    }
    if (textSnippet.GetText().Contains(wxS("\n"))) {
      retval += wxS("\\pard\\s21\\li1105\\lin1105\\f0\\fs24 ");
    }
  }
  retval += wxString::Format(wxS("\\cf%li "), static_cast<long>(TS_CODE_DEFAULT));
  return retval;
}

wxString EditorCell::ToRTF() const {
  wxString retval;

  switch (m_type) {
  case MC_TYPE_TITLE:
    retval += wxS("\\pard\\s16\\b\\f0\\fs56 ") + RTFescape(m_text) + wxS("\n");
    break;
  case MC_TYPE_SECTION:
    retval += wxS("\\pard\\s1\\b\\f0\\fs40 ") + RTFescape(m_text) + wxS("\n");
    break;
  case MC_TYPE_SUBSECTION:
    retval += wxS("\\pard\\s2\\b\\f0\\fs36 ") + RTFescape(m_text) + wxS("\n");
    break;
  case MC_TYPE_SUBSUBSECTION:
    retval += wxS("\\pard\\s3\\b\\f0\\fs32 ") + RTFescape(m_text) + wxS("\n");
    break;
  case MC_TYPE_HEADING5:
    retval += wxS("\\pard\\s4\\b\\f0\\fs32 ") + RTFescape(m_text) + wxS("\n");
    break;
  case MC_TYPE_HEADING6:
    retval += wxS("\\pard\\s5\\b\\f0\\fs32 ") + RTFescape(m_text) + wxS("\n");
    break;
  case MC_TYPE_TEXT:
    retval += wxS("\\pard\\s0 ") + RTFescape(m_text, true);
    break;
  // MC_TYPE_INPUT and MC_TYPE_PROMPT are code types (see IsCodeType); they
  // never occur here since CodeEditorCell overrides ToRTF().
  default:
    retval += wxS("\\pard\\s0 ") + RTFescape(m_text);
    break;
  }
  return retval;
}

wxString EditorCell::ToTeX() const {
  wxString text = m_text;
  if (!text.StartsWith(wxS("TeX:"))) {
    text.Replace(wxS("\u00a0"), wxS("~"));
    text.Replace(wxS("\\"), wxS("\\ensuremath{\\backslash}"));
    text.Replace(wxS("^"), wxS("\\^ "));
    text.Replace(wxS("\u00B0"), wxS("\\ensuremath{^\\circ}"));
    text.Replace(wxS("\u2212"), wxS("-")); // unicode minus sign
    text.Replace(wxS("\u2052"), wxS("-")); // commercial minus sign
    text.Replace(wxS("\uFE63"), wxS("-")); // unicode small minus sign
    text.Replace(wxS("\uFF0D"), wxS("-")); // unicode big minus sign
    text.Replace(wxS("\uFF0B"), wxS("+")); // unicode big plus
    text.Replace(wxS("\uFB29"), wxS("+")); // hebrew alternate plus
    text.Replace(wxS("\u03B1"), wxS("\\ensuremath{\\alpha}"));
    text.Replace(wxS("\u00B1"), wxS("\\ensuremath{\\pm}"));
    text.Replace(wxS("\u00B2"), wxS("\\ensuremath{^2}"));
    text.Replace(wxS("\u00B3"), wxS("\\ensuremath{^3}"));
    text.Replace(wxS("\u221A"), wxS("\\ensuremath{\\sqrt{}}"));
    text.Replace(wxS("\u2148"), wxS("\\ensuremath{\\mathbbm{i}}"));
    text.Replace(wxS("\u2147"), wxS("\\ensuremath{\\mathbbm{e}}"));
    text.Replace(wxS("\u210f"), wxS("\\ensuremath{\\hbar}"));
    text.Replace(wxS("\u2203"), wxS("\\ensuremath{\\exists}"));
    text.Replace(wxS("\u2204"), wxS("\\ensuremath{\\nexists}"));
    text.Replace(wxS("\u2208"), wxS("\\ensuremath{\\in}"));
    text.Replace(wxS("\u21D2"), wxS("\\ensuremath{\\Longrightarrow}"));
    text.Replace(wxS("\u221e"), wxS("\\ensuremath{\\infty}"));
    text.Replace(wxS("\u22C0"), wxS("\\ensuremath{\\wedge}"));
    text.Replace(wxS("\u22C1"), wxS("\\ensuremath{\\vee}"));
    text.Replace(wxS("\u22bb"), wxS("\\ensuremath{\\oplus}"));
    text.Replace(wxS("\u22BC"), wxS("\\ensuremath{\\overline{\\wedge}}"));
    text.Replace(wxS("\u22BB"), wxS("\\ensuremath{\\overline{\\vee}}"));
    text.Replace(wxS("\u00AC"), wxS("\\ensuremath{\\setminus}"));
    text.Replace(wxS("\u22C3"), wxS("\\ensuremath{\\cup}"));
    text.Replace(wxS("\u22C2"), wxS("\\ensuremath{\\cap}"));
    text.Replace(wxS("\u2286"), wxS("\\ensuremath{\\subseteq}"));
    text.Replace(wxS("\u2282"), wxS("\\ensuremath{\\subset}"));
    text.Replace(wxS("\u2288"), wxS("\\ensuremath{\\not\\subseteq}"));
    text.Replace(wxS("\u0127"), wxS("\\ensuremath{\\hbar}"));
    text.Replace(wxS("\u0126"), wxS("\\ensuremath{\\Hbar}"));
    text.Replace(wxS("\u2205"), wxS("\\ensuremath{\\emptyset}"));
    text.Replace(wxS("\u00BD"), wxS("\\ensuremath{\\frac{1}{2}}"));
    text.Replace(wxS("\u03B2"), wxS("\\ensuremath{\\beta}"));
    text.Replace(wxS("\u03B3"), wxS("\\ensuremath{\\gamma}"));
    text.Replace(wxS("\u03B4"), wxS("\\ensuremath{\\delta}"));
    text.Replace(wxS("\u03B5"), wxS("\\ensuremath{\\epsilon}"));
    text.Replace(wxS("\u03B6"), wxS("\\ensuremath{\\zeta}"));
    text.Replace(wxS("\u03B7"), wxS("\\ensuremath{\\eta}"));
    text.Replace(wxS("\u03B8"), wxS("\\ensuremath{\\theta}"));
    text.Replace(wxS("\u03B9"), wxS("\\ensuremath{\\iota}"));
    text.Replace(wxS("\u03BA"), wxS("\\ensuremath{\\kappa}"));
    text.Replace(wxS("\u03BB"), wxS("\\ensuremath{\\lambda}"));
    text.Replace(wxS("\u03BC"), wxS("\\ensuremath{\\mu}"));
    text.Replace(wxS("\u03BD"), wxS("\\ensuremath{\\nu}"));
    text.Replace(wxS("\u03BE"), wxS("\\ensuremath{\\xi}"));
    text.Replace(wxS("\u03BF"), wxS("o"));
    text.Replace(wxS("\u03C0"), wxS("\\ensuremath{\\pi}"));
    text.Replace(wxS("\u03C1"), wxS("\\ensuremath{\\rho}"));
    text.Replace(wxS("\u03C3"), wxS("\\ensuremath{\\sigma}"));
    text.Replace(wxS("\u03C4"), wxS("\\ensuremath{\\tau}"));
    text.Replace(wxS("\u03C5"), wxS("\\ensuremath{\\upsilon}"));
    text.Replace(wxS("\u03C6"), wxS("\\ensuremath{\\phi}"));
    text.Replace(wxS("\u03C7"), wxS("\\ensuremath{\\chi}"));
    text.Replace(wxS("\u03C8"), wxS("\\ensuremath{\\psi}"));
    text.Replace(wxS("\u03C9"), wxS("\\ensuremath{\\omega}"));
    text.Replace(wxS("\u0391"), wxS("A"));
    text.Replace(wxS("\u0392"), wxS("B"));
    text.Replace(wxS("\u0393"), wxS("\\ensuremath{\\Gamma}"));
    text.Replace(wxS("\u0394"), wxS("\\ensuremath{\\Delta}"));
    text.Replace(wxS("\u0395"), wxS("E"));
    text.Replace(wxS("\u0396"), wxS("Z"));
    text.Replace(wxS("\u0397"), wxS("H"));
    text.Replace(wxS("\u0398"), wxS("\\ensuremath{\\Theta}"));
    text.Replace(wxS("\u0399"), wxS("I"));
    text.Replace(wxS("\u039A"), wxS("K"));
    text.Replace(wxS("\u039B"), wxS("\\ensuremath{\\Lambda}"));
    text.Replace(wxS("\u039C"), wxS("M"));
    text.Replace(wxS("\u039D"), wxS("N"));
    text.Replace(wxS("\u039E"), wxS("\\ensuremath{\\Xi}"));
    text.Replace(wxS("\u039F"), wxS("O"));
    text.Replace(wxS("\u03A0"), wxS("\\ensuremath{\\Pi}"));
    text.Replace(wxS("\u03A1"), wxS("P"));
    text.Replace(wxS("\u03A3"), wxS("\\ensuremath{\\Sigma}"));
    text.Replace(wxS("\u03A4"), wxS("T"));
    text.Replace(wxS("\u03A5"), wxS("\\ensuremath{\\Upsilon}"));
    text.Replace(wxS("\u03A6"), wxS("\\ensuremath{\\Phi}"));
    text.Replace(wxS("\u03A7"), wxS("X"));
    text.Replace(wxS("\u03A8"), wxS("\\ensuremath{\\Psi}"));
    text.Replace(wxS("\u03A9"), wxS("\\ensuremath{\\Omega}"));
    text.Replace(wxS("\u2202"), wxS("\\ensuremath{\\partial}"));
    text.Replace(wxS("\u222b"), wxS("\\ensuremath{\\int}"));
    text.Replace(wxS("\u2245"), wxS("\\ensuremath{\\approx}"));
    text.Replace(wxS("\u221d"), wxS("\\ensuremath{\\propto}"));
    text.Replace(wxS("\u2260"), wxS("\\ensuremath{\\neq}"));
    text.Replace(wxS("\u2264"), wxS("\\ensuremath{\\leq}"));
    text.Replace(wxS("\u2265"), wxS("\\ensuremath{\\geq}"));
    text.Replace(wxS("\u226A"), wxS("\\ensuremath{\\ll}"));
    text.Replace(wxS("\u226B"), wxS("\\ensuremath{\\gg}"));
    text.Replace(wxS("\u220e"), wxS("\\ensuremath{\\blacksquare}"));
    text.Replace(wxS("\u2263"), wxS("\\ensuremath{\\equiv}"));
    text.Replace(wxS("\u2211"), wxS("\\ensuremath{\\sum}"));
    text.Replace(wxS("\u220F"), wxS("\\ensuremath{\\prod}"));
    text.Replace(wxS("\u2225"), wxS("\\ensuremath{\\parallel}"));
    text.Replace(wxS("\u27C2"), wxS("\\ensuremath{\\bot}"));
    text.Replace(wxS("~"), wxS("\\ensuremath{\\sim }"));
    text.Replace(wxS("_"), wxS("\\_"));
    text.Replace(wxS("$"), wxS("\\$"));
    text.Replace(wxS("%"), wxS("\\%"));
    text.Replace(wxS("&"), wxS("\\&"));
    text.Replace(wxS("@"), wxS("\\ensuremath{@}"));
    text.Replace(wxS("#"), wxS("\\ensuremath{\\neq}"));
    text.Replace(wxS("\u00A0"), wxS("~")); // A non-breakable space
    text.Replace(wxS("<"), wxS("\\ensuremath{<}"));
    text.Replace(wxS(">"), wxS("\\ensuremath{>}"));
    text.Replace(wxS("\u219D"), wxS("\\ensuremath{\\leadsto}"));
    text.Replace(wxS("\u2192"), wxS("\\ensuremath{\\rightarrow}"));
    text.Replace(wxS("\u27F6"), wxS("\\ensuremath{\\longrightarrow}"));
    // Now we might want to introduce some markdown:
    MarkDownTeX MarkDown(m_configuration);
    if (m_type != MC_TYPE_INPUT)
      text = MarkDown.MarkDown(text);
    else {
      text.Replace(wxS("\n"), wxS("\\\\\n"));
      text.Replace(wxS(" "), wxS("\\ "));
    }
  } else {
    text = text.Mid(5, text.Length());
  }
  return text;
}

wxString EditorCell::ToXML() const {
  wxString xmlstring = XMLescape(m_text);
  // Handle hard and soft line breaks separately
  xmlstring.Replace(wxS("&#0A;"), wxS("</line>\n<line>"));
  xmlstring.Replace(wxS("&#0D;"), wxS(" "));
  xmlstring = wxS("<line>") + xmlstring + wxS("</line>\n");
  wxString head = wxS("<editor");
  head += GetXMLFlags();
  head += XMLTypeAttribute();
  head += wxS(">\n");

  return head + xmlstring + wxS("</editor>\n");
}

wxString EditorCell::XMLTypeAttribute() const {
  switch (m_type) {
  case MC_TYPE_TEXT:
    return wxS(" type=\"text\"");
  case MC_TYPE_TITLE:
    return wxS(" type=\"title\" sectioning_level=\"1\"");
  case MC_TYPE_SECTION:
    return wxS(" type=\"section\" sectioning_level=\"2\"");
  case MC_TYPE_SUBSECTION:
    return wxS(" type=\"subsection\" sectioning_level=\"3\"");
  case MC_TYPE_SUBSUBSECTION:
    // We save subsubsections as subsections with a higher sectioning level:
    // This makes them backwards-compatible in the way that they are displayed
    // as subsections on old wxMaxima installations.
    return wxS(" type=\"subsection\" sectioning_level=\"4\"");
  case MC_TYPE_HEADING5:
    return wxS(" type=\"subsection\" sectioning_level=\"5\"");
  case MC_TYPE_HEADING6:
    return wxS(" type=\"subsection\" sectioning_level=\"6\"");
  case MC_TYPE_INPUT:
  default:
    return wxS(" type=\"input\"");
  }
}

void EditorCell::ConvertNumToUNicodeChar() {
  if (CursorPosition() == 0)
    return;
  int numLen = 0;
  wxString::const_iterator it = m_text.begin() + CursorPosition();
  while (it != m_text.begin()) {
    --it;
    wxChar ch = *it;
    if (((ch >= '0') && (ch <= '9')) ||
        ((ch >= 'a') && (ch <= 'f')) ||
        ((ch >= 'A') && (ch <= 'F'))) {
      numLen++;
      CursorMove(-1);
    } else
      break;
  }

  wxString numString =
    m_text.SubString(CursorPosition(),
                     CursorPosition() + numLen - 1);
  long number;
  if (!numString.ToLong(&number, 16))
    return;

  wxString newChar;
  {
    wxLogNull suppressConversationErrors;
    newChar = wxChar(number);
  }
  m_text = m_text.Left(CursorPosition()) + newChar +
    m_text.Right(m_text.Length() - CursorPosition() - numLen);
  CursorMove(newChar.Length());
}

bool EditorCell::NeedsRecalculation(AFontSize fontSize) const {
  if (Cell::NeedsRecalculation(fontSize))
    return true;
  if (m_isDirty) {
    Configuration::g_stats.recalculationNeeded_EditorDirty++;
    return true;
  }
  return false;
}

void EditorCell::Recalculate(AFontSize fontsize) const {
  if(NeedsRecalculation(fontsize))
    {
      // Needs to be before the StyleText() as it sets m_fontsize_scaled
      Cell::Recalculate(fontsize);
      // Perhaps we should test if the fons have actually changed here.
      FontsChanged();
      m_isDirty = false;
      m_widths.clear();
      StyleText();
      SetFont(m_configuration->GetRecalcDC());

      // Measure the text height using characters that might extend below or above
      // the region ordinary characters move in.
      wxCoord charWidth;
      m_configuration->GetRecalcDC()->GetTextExtent(wxS("äXÄgy"), &charWidth, &m_charHeight);

      // We want a little bit of vertical space between two text lines (and between
      // two labels).
      m_charHeight += 2 * MC_TEXT_PADDING;
      wxCoord width = 0, tokenwidth, tokenheight, linewidth = 0;

      if (m_text == wxEmptyString)
        width = charWidth;

      m_numberOfLines = 1;

      for (auto &textSnippet : m_styledText) {
        if ((textSnippet.GetText().StartsWith(wxS('\n')) ||
             (textSnippet.GetText().StartsWith(wxS('\r'))))) {
          m_numberOfLines++;
          linewidth = textSnippet.GetIndentPixels();
        } else if (textSnippet.GetText() == wxS("\t")) {
          // A tab's width depends on where it starts on the line, so unlike
          // every other token it must never be cached via SetWidth().
          linewidth = NextTabStop(linewidth);
          width = std::max(width, linewidth);
        } else {
          m_configuration->GetRecalcDC()->GetTextExtent(textSnippet.GetText(),
                                                        &tokenwidth, &tokenheight);
          textSnippet.SetWidth(tokenwidth);
          linewidth += tokenwidth;
          width = std::max(width, linewidth);
        }
      }

      // Handle folding
      if (FirstLineOnlyEditor())
        m_numberOfLines = 1;

      // Add a line border
      m_width = width + 2 * Scale_Px(2);

      // Calculate the cell height
      if (FirstLineOnlyEditor())
        m_height = m_charHeight + 2 * Scale_Px(2);
      else
        m_height =
            m_numberOfLines * m_charHeight + 2 * static_cast<size_t>(Scale_Px(2));

      if (m_height < m_charHeight + 2 * Scale_Px(2))
        m_height = (m_charHeight) + 2 * Scale_Px(2);

      // The center is in the middle of the 1st line
      m_center = m_charHeight / 2;
      m_height = std::max(m_height.GetOrElse(0), m_charHeight + 2 * Scale_Px(2));

      // Our height/center just changed. The cached list geometry (GetHeightList(),
      // GetCenterList(), GetMaxDrop()) of this cell and of every preceding cell on
      // the same draw list -- e.g. the "(%i1)" prompt label that the containing
      // GroupCell sums up to find its input height -- now has to be recomputed.
      // Recalculate() does not go through ResetSize(), so we invalidate it here;
      // otherwise the GroupCell keeps the old input height and does not grow when
      // the user adds a line to an (already evaluated) input cell.
      InvalidateListCache();

      m_containsChanges = false;
    }
}

wxString EditorCell::ToHTML() const {
  wxString retval;

  for (const auto &tmp : OnList(this)) {
    for (const auto &textSnippet : tmp.m_styledText) {
      wxString text = PrependNBSP(EscapeHTMLChars(textSnippet.GetText()));

      if (textSnippet.IsStyleSet()) {
        switch (textSnippet.GetTextStyle()) {
        case TS_CODE_COMMENT:
          retval +=
            wxS("<span class=\"code_comment\">") + text + wxS("</span>");
          break;
        case TS_CODE_VARIABLE:
          retval +=
            wxS("<span class=\"code_variable\">") + text + wxS("</span>");
          break;
        case TS_CODE_FUNCTION:
          retval +=
            wxS("<span class=\"code_function\">") + text + wxS("</span>");
          break;
        case TS_CODE_NUMBER:
          retval += wxS("<span class=\"code_number\">") + text + wxS("</span>");
          break;
        case TS_CODE_STRING:
          retval += wxS("<span class=\"code_string\">") + text + wxS("</span>");
          break;
        case TS_CODE_OPERATOR:
          retval +=
            wxS("<span class=\"code_operator\">") + text + wxS("</span>");
          break;
        case TS_CODE_LISP:
          retval += wxS("<span class=\"code_lisp\">") + text + wxS("</span>");
          break;
        case TS_CODE_ENDOFLINE:
        default:
          retval +=
            wxS("<span class=\"code_endofline\">") + text + wxS("</span>");
          break;
        }
      } else
        retval += text;
    }
  }
  return retval;
}

wxCoord EditorCell::SelectionRunLeft(size_t runStart, size_t runEnd,
                                     wxCoord runStartX, wxCoord runWidth) {
  size_t column, line;
  PositionToXY(runStart, &column, &line);
  if (!LineIsRightToLeft(line) || LineIsMixedDirection(line))
    return runStartX;

  // The run reads right to left, so runStart sits at its right edge: the left
  // edge is one run-width further left. Deriving it from runStartX rather than
  // from PositionToPoint(runEnd) keeps the rectangle exactly as wide as the
  // text it covers, even where the two differ by a rounding.
  (void)runEnd;
  return runStartX - runWidth;
}

void EditorCell::MarkSelection(wxDC *dc, size_t start, size_t end, TextStyle style) {
  if (start > m_text.Length())
    start = m_text.Length();
  if (end > m_text.Length())
    end = m_text.Length();
  if (start >= end)
    return;

  wxPoint point;
  size_t pos_left = start;

#if defined(__WXOSX__)
  dc->SetPen(wxNullPen); // no border on rectangles
#else
  dc->SetPen(*(wxThePenList->FindOrCreatePen(
                                             m_configuration->GetColor(style), 1, wxPENSTYLE_SOLID)));
  // window linux, set a pen
#endif
  dc->SetBrush(*(wxTheBrushList->FindOrCreateBrush(
                                                   m_configuration->GetColor(style)))); // highlight c.
  // Walk the selection one display line at a time, drawing a rect per line.
  // Display lines are bounded by hard newlines (a '\n' in m_text) and by soft
  // breaks (the zero-width entries in m_softBreaks); the latter no longer sit
  // in the content, so we consult the side table for them.
  size_t pos = pos_left;
  while (pos < end) {
    size_t lineStart = pos;
    while ((pos < end) && (m_text[pos] != '\n') &&
           !((pos != lineStart) && IsSoftBreakBefore(pos)))
      pos++;

    point = PositionToPoint(lineStart); // left edge (includes any indentation)
    wxCoord selectionWidth =
      MeasureTextWidth(point.x,
                       m_text.SubString(lineStart, pos > lineStart ? pos - 1 : lineStart));
    if (pos == lineStart) // empty line
      selectionWidth = 0;

    // On a right-to-left line the selected run is drawn from its *end*
    // leftwards, so the first selected character is at the run's right edge and
    // the rectangle has to start at the last one instead. Taking
    // PositionToPoint(lineStart) as the left edge there would highlight the
    // text that follows the selection rather than the selection itself.
    size_t dispColumn, dispLine;
    PositionToXY(lineStart, &dispColumn, &dispLine);
    if (LineIsMixedDirection(dispLine) && Bidi::IsAvailable()) {
      // SelectionRunLeft() below only knows the *line's* direction, which
      // isn't enough here; PositionToPoint() itself does know the run
      // [lineStart, pos) is in (see MixedDirectionOffset()), for lineStart
      // and pos independently, so the rectangle is simply the span between
      // them. That also covers a range spanning more than one run as a
      // single bounding rectangle - the same approximation a wholly
      // right-to-left line already makes.
      const wxCoord endX = PositionToPoint(pos).x;
      selectionWidth = (endX > point.x) ? (endX - point.x) : (point.x - endX);
      point.x = std::min(point.x, endX);
    } else
      point.x = SelectionRunLeft(lineStart, pos, point.x, selectionWidth);

    wxRect rect;
#if defined(__WXOSX__)
    rect = GetRect(); // rectangle representing the cell
    if ((pos < end) && (m_text[pos] == '\n')) // draw to the right border (mac)
      selectionWidth = rect.GetRight() - point.x;
#endif

    rect = wxRect(point.x, point.y + Scale_Px(1) - m_center, selectionWidth,
                  m_charHeight);
    // draw the rectangle if it is in the region that is to be updated.
    if (m_configuration->InUpdateRegion(rect))
      dc->DrawRectangle(CropToUpdateRegion(rect));

    // A hard newline is a real character and is consumed; a soft break is
    // zero-width, so the next line simply starts at the break offset.
    if ((pos < end) && (m_text[pos] == '\n'))
      pos++;
  }
}

/* Draws the editor cell including selection and cursor

   The order this cell is drawn is:
   1. Mark text that coincides with the selection
   2. mark the diff viewer's changed-text ranges (wxCOPY), TS_DIFF_CHANGED color
   3. draw selection (wxCOPY), TS_SELECTION color
   4. mark matching parenthesis (wxCOPY), TS_SELECTION color
   5. draw all text (wxCOPY)
   6. draw the caret (wxCOPY), TS_CURSOR color

   The text is not taken from m_text but from the list of styled text snippets
   StyleText() converts m_text into. This way the decisions needed for styling
   text are cached for later use.
*/
void EditorCell::SetCurrentPoint(wxPoint point) const {
  Cell::SetCurrentPoint(point);
}

void EditorCell::Draw(wxDC *dc, wxDC *antialiassingDC) {
  Cell::Draw(dc, antialiassingDC);

  if (!IsHidden() && (DrawThisCell())) {
    wxPoint point = GetCurrentPoint();
    wxRect rect = GetRect();
    if (m_height.IsInvalid() || m_width.IsInvalid())
      return;
    wxCoord y = rect.GetY();
    if((m_height < 1) || (m_width < 1) || (y < 0))
      return;

    // Set the background to the cell's background color
    if (GetTextStyle() == TS_TEXT) {
      if (m_configuration->InUpdateRegion(rect) &&
          (m_configuration->EditorBackgroundColor() !=
           m_configuration->DefaultBackgroundColor()))
        {
          wxBrush *br;
          br = wxTheBrushList->FindOrCreateBrush(m_configuration->EditorBackgroundColor());
          dc->SetBrush(*br);
          dc->SetPen(*wxTRANSPARENT_PEN);
          auto width = m_configuration->GetCanvasSize().GetWidth() - rect.x;
          rect.SetWidth(width);
          dc->DrawRectangle(CropToUpdateRegion(rect));
        }
    }
    SetFont(dc);

    m_selectionChanged = false;

    //
    // Mark text that coincides with the selection
    //
    if (!m_documentCellPointers->GetSelectionString().IsEmpty()) {
      long long start = 0;
      const wxString &text = m_text;
      while ((start = text.find(m_documentCellPointers->GetSelectionString(), start)) !=
             wxNOT_FOUND) {
        size_t end = static_cast<size_t>(start) + m_documentCellPointers->GetSelectionString().Length();

        // Mark only text that won't be marked in the next step:
        // This would not only be unnecessary but also could cause
        // selections to flicker in very long texts
        if ((!IsActive()) || (static_cast<size_t>(start) != SelectionLeft()))
          MarkSelection(dc, static_cast<size_t>(start), end, TS_EQUALSSELECTION);
        if (m_documentCellPointers->GetSelectionString().Length() == 0)
          end++;
        start = end;
      }
    }

    //
    // Mark the ranges the diff viewer flagged as differing. The stored ranges
    // are only valid for the text they were computed from; every edit changes
    // the text's length (soft-wrapping doesn't - it swaps ' ' and '\r' in
    // place), so a length mismatch means "stale, don't draw".
    //
    if (!m_diffHighlights.empty() &&
        m_text.Length() == m_diffHighlightsTextLength) {
      for (const auto &range : m_diffHighlights)
        MarkSelection(dc, range.first, range.second, TS_DIFF_CHANGED);
    }

    if (IsActive()) // draw selection or matching parens
      {
        //
        // Mark selection
        //
        if (SelectionActive())
          MarkSelection(dc, SelectionLeft(),
                        SelectionRight(), TS_SELECTION);

        //
        // Matching parens - draw only if we don't have selection
        //
        else if ((m_paren1 != -1 && m_paren2 != -1) &&
                 (m_configuration->ShowMatchingParens())) {
          {

#if defined(__WXOSX__)
            dc->SetPen(wxNullPen); // no border on rectangles
#else
            dc->SetPen(*(wxThePenList->FindOrCreatePen(m_configuration->GetColor(TS_SELECTION), 1,
                                                       wxPENSTYLE_SOLID))); // window linux, set a pen
#endif
            dc->SetBrush(*(wxTheBrushList->FindOrCreateBrush(m_configuration->GetColor(TS_SELECTION)))); // highlight c.
          }
          if (m_paren1 >= 0 && static_cast<size_t>(m_paren1) < m_text.Length() &&
              m_paren2 >= 0 && static_cast<size_t>(m_paren2) < m_text.Length()) {
            wxPoint matchPoint = PositionToPoint(m_paren1);
            wxCoord width, height;
            dc->GetTextExtent(m_text.at(m_paren1), &width, &height);
            wxRect matchRect(matchPoint.x + 1,
                             matchPoint.y + Scale_Px(2) - m_center + 1,
                             width - 1, height - 1);
            if (m_configuration->InUpdateRegion(matchRect))
              dc->DrawRectangle(CropToUpdateRegion(matchRect));
            matchPoint = PositionToPoint(m_paren2);
            dc->GetTextExtent(m_text.at(m_paren2), &width, &height);
            matchRect = wxRect(matchPoint.x + 1,
                               matchPoint.y + Scale_Px(2) - m_center + 1,
                               width - 1, height - 1);
            if (m_configuration->InUpdateRegion(matchRect))
              dc->DrawRectangle(CropToUpdateRegion(matchRect));
          }
        } // else if (m_paren1 != -1 && m_paren2 != -1)
      }   // if (IsActive())

    //
    // Draw the text
    //

    wxRect updateRegion = m_configuration->GetUpdateRegion();
    wxPoint TextStartingpoint = point;
    wxPoint TextCurrentPoint = TextStartingpoint;
    int lastStyle = -1;
    size_t lastIndent = 0;
    // In a right-to-left document only the point each line *starts* at moves:
    // the snippets keep their order, since that order is already the logical
    // one and the toolkit shapes and orders the text inside each of them.
    const std::vector<wxCoord> rtlLineOffsets = RightToLeftLineOffsets();
    size_t rtlLine = 0;
    if (!rtlLineOffsets.empty())
      TextCurrentPoint.x += rtlLineOffsets.front();
    for (auto &textSnippet : m_styledText) {
      wxCoord width;

      // A newline is a separate token.
      if ((textSnippet.GetText() == wxS("\n")) || (textSnippet.GetText() == wxS("\r"))) {
        if ((textSnippet.GetText() == wxS("\n")))
          lastIndent = textSnippet.GetIndentPixels();

        // A newline =>
        // set the point to the beginning of the next line.
        TextCurrentPoint.x = TextStartingpoint.x;
        TextCurrentPoint.y += m_charHeight;
        TextCurrentPoint.x += textSnippet.GetIndentPixels();
        if (++rtlLine < rtlLineOffsets.size())
          TextCurrentPoint.x += rtlLineOffsets.at(rtlLine);
      } else if (textSnippet.GetText() == wxS("\t")) {
        // A tab draws no glyph of its own -- it just advances to the next
        // tab stop, measured from the current line's own start (including
        // its indentation).
        const wxCoord lineOrigin = TextStartingpoint.x + lastIndent;
        TextCurrentPoint.x =
          lineOrigin + NextTabStop(TextCurrentPoint.x - lineOrigin);
      } else {
        // We need to draw some text.

        // Grab a pen of the right color.
        if (lastStyle != textSnippet.GetTextStyle()) {
          dc->SetTextForeground(m_configuration->GetColor(textSnippet.GetTextStyle()));
          lastStyle = textSnippet.GetTextStyle();
        }

        // Draw a char that shows we continue an indentation - if this is
        // needed.
        if (!textSnippet.GetIndentChar().IsEmpty())
          dc->DrawText(textSnippet.GetIndentChar(),
                       TextStartingpoint.x + lastIndent,
                       TextCurrentPoint.y - m_center);

        // Determine the box the will be is in.
        if (!textSnippet.SizeKnown()) {
          wxCoord height;
          dc->GetTextExtent(textSnippet.GetText(), &width, &height);
          textSnippet.SetWidth(width);
        } else
          width = textSnippet.GetWidth();
        wxRect textRect(TextCurrentPoint.x, TextCurrentPoint.y - m_center,
                        width, m_charHeight);

        // Draw the text only if it overlaps the update region
        if (((!m_configuration->ClipToDrawRegion())) ||
            (updateRegion.Intersects(textRect)))
          dc->DrawText(textSnippet.GetText(), TextCurrentPoint.x,
                       TextCurrentPoint.y - m_center);
        TextCurrentPoint.x += width;
      }
    }
    //
    // Draw the caret
    //

    if (m_displayCaret && m_hasFocus && IsActive()) {
      size_t caretInLine = 0;
      size_t caretInColumn = 0;
      PositionToXY(CursorPosition(), &caretInColumn, &caretInLine);

      size_t lineWidth = GetLineWidth(caretInLine, caretInColumn);
      dc->SetPen(*(wxThePenList->FindOrCreatePen(m_configuration->GetColor(TS_CURSOR),
                                                 1, wxPENSTYLE_SOLID)));
      dc->SetBrush(*(wxTheBrushList->FindOrCreateBrush(m_configuration->GetColor(TS_CURSOR),
                                                       wxBRUSHSTYLE_SOLID)));
      dc->DrawRectangle(
                        point.x + lineWidth - m_configuration->GetCursorWidth(),
                        point.y + Scale_Px(1) - m_center + caretInLine * m_charHeight,
                        m_configuration->GetCursorWidth(), m_charHeight - Scale_Px(5));
    }
  }
}

void EditorCell::SetType(CellType type) {
  m_widths.clear();
  Cell::SetType(type);
}

void EditorCell::SetStyle(TextStyle style) {
  m_widths.clear();
  Cell::SetStyle(style);
}

void EditorCell::SetFont(wxDC *dc) const {
  if(!dc)
    return;
  const wxFont &font = GetFont();
  if(!dc->GetFont().IsSameAs(font))
    dc->SetFont(font);
}

wxSize EditorCell::GetTextSize(wxString const &text) const {
  const wxDC * const dc = m_configuration->GetRecalcDC();

  // If we already know this text piece's size we return the cached value
  if (auto it = m_widths.find(text); it != m_widths.end())
    return it->second;

  // Ask wxWidgets to return this text piece's size (slow!)
  wxSize sz = dc->GetTextExtent(text);
  m_widths[text] = sz;
  return sz;
}

void EditorCell::SetForeground(wxDC *dc) {
  dc->SetTextForeground(m_configuration->GetColor(GetTextStyle()));
}

wxString EditorCell::GetCurrentCommand() const {
  // Discard all chars behind the cursor.
  wxString lineTillCursor = m_text.Left(CursorPosition());

  wxString command;
  wxString possibleCommand;
  wxString::const_iterator it = lineTillCursor.begin();
  while (it != lineTillCursor.end()) {
    if (wxIsalpha(*it) || (*it == wxS('_')) || (*it == wxS('\\'))) {
      if (*it == '\\') {
        possibleCommand += *it;
        ++it;
      }
      if (it != lineTillCursor.end()) {
        possibleCommand += *it;
        ++it;
      }
      while ((it != lineTillCursor.end()) &&
             ((wxIsalnum(*it) || (*it == wxS('_')) || (*it == wxS('\\'))))) {
        if (*it == '\\') {
          possibleCommand += *it;
          ++it;
        }
        if (it != lineTillCursor.end()) {
          possibleCommand += *it;
          ++it;
        }
      }
    } else
      switch (wxChar(*it)) {
      case ' ':
      case '\t':
      case '\n':
      case '\r':
        while ((it != lineTillCursor.end()) &&
               ((*it == wxS(' ')) || (*it == wxS('\t')) || (*it == wxS('\n')) ||
                (*it == wxS('\r'))))
          ++it;
        if ((it != lineTillCursor.end()) && (*it == wxS('('))) {
          command = possibleCommand;
          possibleCommand.Clear();
          ++it;
        }
        break;
      case '(':
        if ((possibleCommand != wxEmptyString))
          command = possibleCommand;
        ++it;
        break;
      case '$':
      case ';': {
        command.Clear();
        possibleCommand.Clear();
        ++it;
        break;
      }
      default:
        possibleCommand.Clear();
        ++it;
        break;
      }
  }
  return command;
}

wxString EditorCell::NormalizeLineEndings(const wxString &input_) {
  wxString input = input_;
  input.Replace(wxS("\r\n"), wxS("\n"));
  return input;
}

wxCoord EditorCell::NextTabStop(wxCoord startX) const {
  const wxCoord tabWidth = 4 * GetTextSize(wxS(" ")).GetWidth();
  if (tabWidth <= 0)
    return startX;
  return ((startX / tabWidth) + 1) * tabWidth;
}

wxCoord EditorCell::MeasureTextWidth(wxCoord startX, const wxString &text) const {
  if (text.Find(wxS('\t')) == wxNOT_FOUND)
    return GetTextSize(text).GetWidth();

  wxCoord x = startX;
  wxString run;
  for (wxString::const_iterator ch = text.begin(); ch != text.end(); ++ch) {
    if (*ch == wxS('\t')) {
      if (!run.empty()) {
        x += GetTextSize(run).GetWidth();
        run.clear();
      }
      x = NextTabStop(x);
    } else
      run += *ch;
  }
  if (!run.empty())
    x += GetTextSize(run).GetWidth();
  return x - startX;
}

size_t EditorCell::BeginningOfLine(size_t pos) const {
  if (m_text.empty())
    return 0;
  if (pos > m_text.size())
    pos = m_text.size();
  // The display line starts at the nearest boundary before pos: the character
  // after the previous hard newline, or the nearest soft break at/below pos
  // (soft breaks are display-line starts, kept in the side table), whichever
  // is closer.
  size_t lineStart = 0;
  for (size_t i = pos; i > 0; --i) {
    if (m_text[i - 1] == wxS('\n')) {
      lineStart = i;
      break;
    }
  }
  auto sb = std::upper_bound(m_softBreaks.begin(), m_softBreaks.end(), pos);
  if (sb != m_softBreaks.begin())
    lineStart = wxMax(lineStart, *(sb - 1));
  return lineStart;
}

size_t EditorCell::EndOfLine(size_t pos) {
  if (pos >= m_text.size())
    return m_text.size();

  // Advance to the next display-line boundary: a hard newline or a soft break.
  // The soft break at pos itself (this line's own start) must not end it.
  const size_t startPos = pos;
  while ((pos < m_text.size()) && (m_text[pos] != wxS('\n')) &&
         !((pos != startPos) && IsSoftBreakBefore(pos)))
    ++pos;

  return pos;
}

#if defined __WXOSX__

bool EditorCell::HandleCtrlCommand(wxKeyEvent &ev) {
  int code = ev.GetKeyCode();
  bool done = true;

  if (code >= 32)
    return false;

  code = code + 'A' - 1;

  switch (code) {
  case 'K': {
    ClearSelection();
    SaveValue();
    size_t end = EndOfLine(CursorPosition());
    if (end == CursorPosition())
      end++;
    m_text = m_text.Left(CursorPosition()) +
      m_text.Mid(end);
    m_isDirty = true;
    break;
  }

  case 'E': {
    ClearSelection();
    size_t end = EndOfLine(CursorPosition());
    if (ev.ShiftDown())
      SetSelection(CursorPosition(), end);
    else
      CursorPosition(end);
    m_displayCaret = true;
    break;
  }

  case 'A': {
    ClearSelection();
    size_t start = BeginningOfLine(CursorPosition());
    if (ev.ShiftDown())
      SetSelection(start, CursorPosition());
    else
      CursorPosition(start);
    m_displayCaret = true;
    break;
  }

  default:
    done = false;
    break;
  }

  return done;
}

#endif // __WXOSX__

void EditorCell::ProcessEvent(wxKeyEvent &event) {
  bool done;
#ifdef __WXOSX__
  done = HandleCtrlCommand(event);
  if (!done)
#endif
    done = HandleSpecialKey(event);

  if ((!done) && (wxIsprint(event.GetUnicodeKey())))
    HandleOrdinaryKey(event);

  if (m_type == MC_TYPE_INPUT)
    FindMatchingParens();

  if (m_isDirty) {
    if (GetGroup())
      GetGroup()->MarkNeedsRecalculate();
    StyleText();
  }
  m_displayCaret = true;
}

size_t EditorCell::GetIndentDepth(wxString text, size_t positionOfCaret) const {
  // Don't indent parenthesis that aren't part of code cells.
  if (m_type != MC_TYPE_INPUT)
    return 0;

  // A list of by how many chars we need to indent the current line.
  std::vector<size_t> indentChars;
  indentChars.push_back(0);

  wxString::const_iterator it = text.begin();

  // Determine how many parenthesis this cell opens or closes before the point
  size_t pos = 0;
  while ((pos < positionOfCaret) && (it < text.end())) {
    wxChar ch = *it;
    if (ch == wxS('\\')) {
      ++pos;
      ++it;
      continue;
    }

    if (ch == wxS('\"')) {
      ++pos;
      ++it;
      while ((it < text.end()) && (pos < positionOfCaret) &&
             (*it != wxS('\"'))) {
        ++pos;
        ++it;
      }
    }

    if ((ch == wxS('(')) || (ch == wxS('[')) || (ch == wxS('{'))) {
      if (indentChars.empty())
        indentChars.push_back(4);
      else
        indentChars.push_back(indentChars.back() + 4);
    }

    if ((ch == wxS(')')) || (ch == wxS(']')) || (ch == wxS('}'))) {
      if (!indentChars.empty())
        indentChars.pop_back();
    }

    // A comma removes all extra indentation from a "do" or an "if".
    if (ch == wxS(',')) {
      // Discard any extra indentation from a "then" or a "do" from the last
      // item of indentChars.
      if (!indentChars.empty()) {
        size_t lst;
        indentChars.pop_back();
        if (!indentChars.empty())
          lst = indentChars.back() + 4;
        else
          lst = 0;
        indentChars.push_back(lst);
      }
    }

    // A semicolon or a dollar sign restarts indentation completely.
    if ((ch == wxS(';')) || (ch == wxS('$'))) {
      // Discard any indentation data
      while (!indentChars.empty())
        indentChars.pop_back();

      // Start fresh with zero indentation.
      indentChars.push_back(0);
    }

    // A "do" or an "if" increases the current indentation level by a tab.
    if ((!wxIsalnum(ch)) || (pos == 0)) {
      // Concatenate the current with the following two characters
      wxString::const_iterator it2(it);
      wxString rest(*it2);
      ++it2;
      if (it2 < text.end()) {
        rest += wxString(*it2);
        ++it2;
        if (it2 < text.end())
          rest += wxString(*it2);
      }
      // Handle a "do"
      if (rest.StartsWith(wxS("do")) &&
          ((rest.Length() < 3) || (!wxIsalnum(rest.at(2))))) {
        size_t lst = 0;
        if (!indentChars.empty()) {
          lst = indentChars.back();
          indentChars.pop_back();
        }
        indentChars.push_back(lst + 4);
      }

      // Handle a "if"
      if (rest.StartsWith(wxS("if")) &&
          ((rest.Length() < 3) || (!wxIsalnum(rest.at(2))))) {
        size_t lst = 0;
        if (!indentChars.empty()) {
          lst = indentChars.back();
          indentChars.pop_back();
        }
        indentChars.push_back(lst + 4);
      }
    }

    if (it < text.end()) {
      ++pos;
      ++it;
    }
  }

  if (it < text.end()) {
    if ((text.at(positionOfCaret) == wxS(')')) ||
        (text.at(positionOfCaret) == wxS(']')) ||
        (text.at(positionOfCaret) == wxS('}'))) {
      if (!indentChars.empty())
        indentChars.pop_back();
    }
  }

  size_t retval;
  if (indentChars.empty())
    retval = 0;
  else
    retval = indentChars.back();

  // A fast way to get the next 5 characters
  wxString rightOfCursor;
  for (int i = 0; i < 5; i++) {
    if (it >= text.end())
      break;

    rightOfCursor += *it;
    ++it;
  }

  rightOfCursor.Trim();
  rightOfCursor.Trim(false);
  if (((rightOfCursor.StartsWith(wxS("else"))) ||
       (rightOfCursor.StartsWith(wxS("then")))) &&
      (rightOfCursor.Length() > 4) && (!(wxIsalnum(rightOfCursor.at(4)))))
    {
      if(retval >= 4)
        retval -= 4;
      else
        retval = 0;
    }

  return retval;
}

void EditorCell::ProcessNewline(bool keepCursorAtStartOfLine) {
  if (SelectionActive()) // we have a selection, delete it, then proceed
    {
      SaveValue();
      auto start = SelectionLeft();
      auto end =   SelectionRight();
      m_text = m_text.Left(start) + m_text.Mid(end);
      CursorPosition(start);
      ClearSelection();
    }

  {
    bool autoIndent = m_configuration->GetAutoIndent();
    // If the cursor is at the beginning of a line we will move it there again
    // after indenting.
    bool cursorAtStartOfLine =
      keepCursorAtStartOfLine &&
      (CursorPosition() == BeginningOfLine(CursorPosition()));

    // If the cursor is part of the whitespace at the beginning of the line
    // we move it to its end if this makes sense.
    if (autoIndent) {
      size_t i = BeginningOfLine(CursorPosition());
      wxString::const_iterator it = m_text.begin() + i;
      wxString::const_iterator it_end = m_text.begin() + CursorPosition();
      while (it < it_end && *it == wxS(' ')) {
        ++i;
        ++it;
      }
      if (i == CursorPosition()) {
        it = m_text.begin() + CursorPosition();
        while (it < m_text.end() && *it == wxS(' ')) {
          CursorMove(1);
          ++it;
        }
      }
    }

    size_t indentChars = GetIndentDepth(m_text, CursorPosition());

    // The string we indent with.
    wxString indentString;
    if (autoIndent && (indentChars > 0))
      for (size_t i = 0; i < indentChars; i++)
        indentString += wxS(" ");

    wxString newLines = m_text.SubString(CursorPosition(), m_text.Length());
    if (autoIndent) {
      // Remove leading spaces from the text that follows the cursor
      wxString newLines_noleadingSpaces;
      newLines_noleadingSpaces.reserve(newLines.Length());
      wxString::const_iterator it = newLines.begin();
      for (; (it != newLines.end()) && (*it == ' '); ++it) {
      }

      for (; it != newLines.end(); ++it)
        newLines_noleadingSpaces += *it;
      newLines = newLines_noleadingSpaces;
    }
    m_text = m_text.Left(CursorPosition()) + wxS("\n") +
      indentString + newLines;
    CursorMove(1);
    if ((indentChars > 0) && (autoIndent)) {
      CursorPosition(BeginningOfLine(CursorPosition()));
      CursorMove(indentChars);
    }
    m_isDirty = true;
    m_containsChanges = true;

    if ((!m_configuration->CursorJump()) ||
        ((cursorAtStartOfLine) && (!autoIndent)))
      CursorPosition(BeginningOfLine(CursorPosition()));
  }
}

bool EditorCell::HandleSpecialKey(wxKeyEvent &event) {
  bool done = true;

  if (((event.GetKeyCode() == 'x') || (event.GetKeyCode() == 'u')) &&
      (event.AltDown())) {
    ConvertNumToUNicodeChar();
    return true;
  }

  // The arrow keys move the caret by what the user sees, not by what is stored.
  // In a right-to-left line the two disagree: the text is drawn in the reverse
  // of its stored order, so stepping *forward* through it moves the caret to the
  // *left* on screen. Pressing Left there therefore has to do what Right does to
  // the stored text, and the other way round -- otherwise the caret walks the
  // wrong way and the key that looks like it should undo the last movement
  // repeats it.
  if ((event.GetKeyCode() == WXK_LEFT) || (event.GetKeyCode() == WXK_RIGHT)) {
    size_t cursorColumn, cursorLine;
    PositionToXY(CursorPosition(), &cursorColumn, &cursorLine);

    bool rightToLeft = false;
    bool decided = false;
    if (LineIsMixedDirection(cursorLine)) {
      // Which way is "forward" on screen depends on the run the caret is
      // actually sitting in, not the line as a whole - the same reason
      // MixedDirectionOffset() exists for PositionToPoint().
      std::vector<BidiRun> runs;
      if (GetLineBidiRuns(cursorLine, &runs)) {
        const size_t pos = CursorPosition();
        for (const auto &r : runs)
          if ((pos >= r.logicalStart) && (pos <= r.logicalEnd)) {
            rightToLeft = r.rightToLeft;
            decided = true;
            break;
          }
      }
    }
    // Otherwise (a wholly one-direction line, or a mixed one libfribidi can't
    // reorder): the line's own direction, if it has one.
    if (!decided)
      rightToLeft = LineIsRightToLeft(cursorLine) && !LineIsMixedDirection(cursorLine);

    if (rightToLeft)
      event.m_keyCode = (event.GetKeyCode() == WXK_LEFT) ? WXK_RIGHT : WXK_LEFT;
  }

  switch (event.GetKeyCode()) {
  case WXK_LEFT:
    {
      SaveValue();
      size_t pos = CursorPosition();
      if (event.ControlDown()) {
        size_t lastpos = CursorPosition();
        while ((pos > 0) &&
               (wxIsalnum(m_text.at(pos - 1)) ||
                m_text.at(pos - 1) == wxS('_') ||
                ((pos > 1) &&
                 (m_text.at(pos - 2) == wxS('\\'))))) {
          if ((pos > 1) &&
              (m_text.at(pos - 2) == wxS('\\')))
            pos--;
          pos--;
        }

        while ((pos > 0) &&
               (wxIsspace(m_text.at(pos - 1))))
          pos--;

        if ((lastpos == pos) && (pos > 0))
          pos--;
      } else if (event.AltDown()) {
        long count = 0;

        while (pos > 0 && count >= 0) {
          pos--;
          if (m_text.at(pos) == '(' ||
              m_text.at(pos) == '[')
            count--;
          else if (m_text.at(pos) == ')' ||
                   m_text.at(pos) == ']')
            count++;
        }
      } else if (pos > 0)
        pos--;

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);
    }
    break;

  case WXK_RIGHT:
    {
      auto pos = CursorPosition();
      SaveValue();

      if (event.ControlDown()) {
        size_t lastpos = pos;

        while ((pos < m_text.Length()) &&
               (wxIsalnum(m_text.at(pos)) ||
                m_text.at(pos) == wxS('_') ||
                m_text.at(pos) == wxS('\\'))) {
          if (m_text.at(pos) == wxS('\\'))
            pos++;
          if (pos < m_text.Length())
            pos++;
        }

        while ((pos < m_text.Length()) &&
               (wxIsspace(m_text.at(pos))))
          pos++;

        if ((pos < m_text.Length()) &&
            (lastpos == pos))
          pos++;
      } else if (event.AltDown()) {
        long long count = 0;

        while (pos < m_text.Length() && count >= 0) {
          pos++;
          if ((m_text.at(pos - 1) == '(') ||
              (m_text.at(pos - 1) == '['))
            count++;
          else if ((m_text.at(pos - 1) == ')') ||
                   (m_text.at(pos - 1) == ']'))
            count--;
        }
      }

      else if (pos < m_text.Length())
        pos++;

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);

      break;
    }
  case WXK_END:
    {
      auto pos = CursorPosition();
      SaveValue();

      if (event.ControlDown()) {
        pos = m_text.Length();
      } else {
        // Stop at the end of the current display line (hard newline or soft
        // break); soft breaks live in the side table, not the content.
        pos = EndOfLine(pos);
      }

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);
      break;
    }
  case WXK_HOME:
    {
      auto pos = CursorPosition();
      SaveValue();

      if (event.ControlDown())
        pos = 0;
      else {
        size_t col, lin;
        PositionToXY(pos, &col, &lin);
        pos = XYToPosition(0, lin);
      }

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);
      break;
    }

  case WXK_PAGEDOWN:
    {
      auto pos = CursorPosition();
      SaveValue();
      size_t column;
      size_t line;
      PositionToXY(pos, &column, &line); // get current line

      if (line < m_numberOfLines - 1) // can we go down ?
        {
          size_t scrolllength = static_cast<size_t>(m_configuration->GetCanvasSize().y) -
            m_charHeight;

          while ((line < m_numberOfLines - 1) && (scrolllength > 0)) {
            line++;
            pos = XYToPosition(column, line);
            scrolllength -= m_charHeight;
          }
        } else { // we can't go down. move caret to the end
        pos = m_text.Length();
      }

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);
      break;
    }

  case WXK_DOWN:
    {
      auto pos = CursorPosition();
      SaveValue();
      size_t column, line;
      PositionToXY(pos, &column, &line); // get current line

      if (line < m_numberOfLines - 1) // can we go down ?
        pos = XYToPosition(column, line + 1);
      else { // we can't go down. move caret to the end
        pos = m_text.Length();
      }

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);
    }
    break;

  case WXK_PAGEUP:
    {
      auto pos = CursorPosition();
      SaveValue();

      size_t column, line;
      PositionToXY(pos, &column, &line); // get current line

      if (line > 0) // can we go up?
        {
          size_t scrolllength = static_cast<size_t>(m_configuration->GetCanvasSize().y) - m_charHeight;

          while ((line > 0) && (scrolllength > 0)) {
            line--;
            pos = XYToPosition(column, line);
            scrolllength -= m_charHeight;
          }
        } else { // we can't move up, move to the beginning
        pos = 0;
      }

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);
    }
    break;

  case WXK_UP:
    {
      auto pos = CursorPosition();
      SaveValue();

      size_t column, line;
      PositionToXY(pos, &column, &line); // get current line

      if (line > 0) // can we go up?
        pos = XYToPosition(column, line - 1);
      else { // we can't move up, move to the beginning
        pos = 0;
      }

      if (event.ShiftDown())
        SelectionEnd(pos);
      else
        CursorPosition(pos);
    }
    break;

  case WXK_RETURN:
    SaveValue();
    ProcessNewline();
    StyleText();
    break;

  case WXK_DELETE:
    // On windows CMD+WXK_BACK is passed to us as CMD+WXK_DELETE.
    if (!event.CmdDown()) {
      SaveValue(History::Action::removeChar);
      if (!SelectionActive()) {
        if (CursorPosition() < m_text.Length()) {
          m_isDirty = true;
          m_containsChanges = true;
          m_text = m_text.SubString(0, CursorPosition() - 1) +
            m_text.SubString(CursorPosition() + 1, m_text.Length());
        }
      } else {
        m_isDirty = true;
        m_containsChanges = true;
        SaveValue();
        auto start = SelectionLeft();
        auto end   = SelectionRight();
        m_text = m_text.Left(start) + m_text.Mid(end);
        CursorPosition(start);
      }
    } else {
      // Ctrl+Backspace is pressed.

      m_containsChanges = true;
      m_isDirty = true;

      size_t pos     = CursorPosition();
      // Delete characters until the end of the current word or number
      while (pos > 0 &&
             wxIsalnum(m_text.at(pos - 1))) {
        pos--;
        m_text = m_text.SubString(0, pos - 1) +
          m_text.SubString(pos + 1, m_text.Length());
      }
      // Delete Spaces, Tabs and Newlines until the next printable character
      while (pos > 0 &&
             wxIsspace(m_text.at(pos - 1))) {
        pos--;
        m_text = m_text.SubString(0, pos - 1) +
          m_text.SubString(pos + 1, m_text.Length());
      }

      // If we didn't delete anything till now delete one single character.
      if (CursorPosition() == pos) {
        pos--;
        m_text = m_text.SubString(0, pos - 1) +
          m_text.SubString(pos + 1, m_text.Length());
      }
      CursorPosition(pos);
    }
    StyleText();
    break;

  case WXK_BACK:
    SaveValue();
    {
      auto pos = CursorPosition();
      if (SelectionActive()) {
        m_containsChanges = true;
        m_isDirty = true;
        auto start = SelectionLeft();
        auto end   = SelectionRight();
        m_text = m_text.Left(start) + m_text.Mid(end);
        CursorPosition(start);
        StyleText();
        break;
      } else {
        if (!event.CmdDown()) {
          // Backspace without Ctrl => Delete one character if there are
          // characters to delete.
          if (pos > 0) {
            m_containsChanges = true;
            m_isDirty = true;

            // Plain single-character delete. This also removes a whole tab
            // in one press, since a tab is one real character in m_text now
            // (no more need for the old special case that gobbled up to 4
            // trailing spaces to emulate deleting a space-expanded tab).
            /// If deleting ( in () then delete both.
            size_t right = pos;
            if (pos < m_text.Length() &&
                m_configuration->GetMatchParens() &&
                ((m_text.at(pos - 1) == '[' &&
                  m_text.at(pos) == ']') ||
                 (m_text.at(pos - 1) == '(' &&
                  m_text.at(pos) == ')') ||
                 (m_text.at(pos - 1) == '{' &&
                  m_text.at(pos) == '}') ||
                 (m_text.at(pos - 1) == '"' &&
                  m_text.at(pos) == '"')))
              right++;
            m_text = m_text.Left(pos - 1) +
              m_text.Mid(right);
            pos--;
          }

        } else {
          // Ctrl+Backspace is pressed.

          m_containsChanges = true;
          m_isDirty = true;

          size_t lastpos = pos;
          // Delete characters until the end of the current word or number
          while (pos > 0 &&
                 wxIsalnum(m_text.at(pos - 1))) {
            pos--;
            m_text = m_text.SubString(0, pos - 1) +
              m_text.SubString(pos + 1, m_text.Length());
          }
          // Delete Spaces, Tabs and Newlines until the next printable character
          while (pos > 0 &&
                 wxIsspace(m_text.at(pos - 1))) {
            pos--;
            m_text = m_text.SubString(0, pos - 1) +
              m_text.SubString(pos + 1, m_text.Length());
          }

          // If we didn't delete anything till now delete one single character.
          if (lastpos == pos) {
            pos--;
            m_text = m_text.SubString(0, pos - 1) +
              m_text.SubString(pos + 1, m_text.Length());
          }
        }
      }
      StyleText();
      CursorPosition(pos);
    }
    break;

  case WXK_TAB:
    {
      auto pos = CursorPosition();

      m_isDirty = true;
      if (!FindNextTemplate(event.ShiftDown())) {
        m_containsChanges = true;
        {
          if (SelectionActive()) {
            // Selection active and Tab
            SaveValue();

            auto start = SelectionLeft();
            auto end   = SelectionRight();
            // GH #2208: see the matching comment in FindNext() -- keep this
            // in size_t/wxString::npos terms rather than converting to a
            // signed type and comparing against wxNOT_FOUND, which only
            // happens to work on a 64-bit build.
            size_t newLineIndex = std::min(m_text.find(wxS('\n'), start),
                                            m_text.find(wxS('\r'), start));

            if (((newLineIndex != wxString::npos) && (newLineIndex < end)) ||
                (m_text.SubString(newLineIndex, start).Trim() == wxEmptyString)) {
              start = BeginningOfLine(start);
              size_t p = start;

              if (end < m_text.Length() && (m_text.at(end) == wxS('\n')))
                end++;

              if (end > m_text.Length())
                end = m_text.Length();

              while (p < end) {
                if (event.ShiftDown()) {
                  if (p < end && m_text.at(p) == wxS('\t')) {
                    m_text.erase(p, 1);
                    if (end > 0)
                      end--;
                  } else {
                    // No leading tab to remove (e.g. an already
                    // space-indented document): fall back to removing up to
                    // 4 leading spaces, as before.
                    for (int i = 0; i < 4; i++) {
                      if (p < end && m_text.at(p) == wxS(' ')) {
                        m_text.erase(p, 1);
                        if (end > 0)
                          end--;
                      } else
                        break;
                    }
                  }
                } else {
                  m_text.insert(p, wxS("\t"));
                  end += 1;
                  p += 1;
                }
                wxString::const_iterator it = m_text.begin() + p;
                while (it != m_text.end() && p < end && *it != wxS('\n') && *it != wxS('\r')) {
                  p++;
                  ++it;
                }
                if (it != m_text.end() && p < end && (*it == wxS('\n') || *it == wxS('\r'))) {
                  p++;
                  ++it;
                }
              }
              // Keep the selection alive (as [start, end)) rather than
              // collapsing it to a caret: CursorPosition(start) would set
              // both selection ends to start, undoing the SetSelection()
              // just above and preventing repeated Tab/Shift+Tab presses
              // from indenting/dedenting further.
              SetSelection(start, end);
            } else {
              m_text.erase(start, end - start);
              CursorPosition(start);
            }
            StyleText();
            break;
          } else {
            if (!event.ShiftDown()) {
              // No selection and Tab was pressed without Shift: insert a
              // real tab character.
              m_text.insert(pos, wxS("\t"));
              pos += 1;
            } else {
              // No selection and Shift+Tab: dedent by one leading tab if
              // there is one; otherwise fall back to removing up to 4
              // leading spaces, for already space-indented documents.
              size_t start = BeginningOfLine(pos);
              if ((start < m_text.Length()) && (m_text.at(start) == wxS('\t'))) {
                m_text.erase(start, 1);
                if (pos > start)
                  pos = start;
              } else if (m_text.SubString(start, start + 3) == wxS("    ")) {
                m_text.erase(start, 4);
                if (pos > start) {
                  pos = start;
                  wxString::const_iterator it = m_text.begin() + pos;
                  while (it != m_text.end() && *it == wxS(' ')) {
                    pos++;
                    ++it;
                  }
                }
              }
            }
          }
        }
      }
      CursorPosition(pos);
    }
    StyleText();
    break;

    /* Ignored keys */
  case WXK_WINDOWS_LEFT:
  case WXK_WINDOWS_RIGHT:
  case WXK_WINDOWS_MENU:
  case WXK_COMMAND:
  case WXK_START:
    break;

  default:
    done = false;
    break;
  }

  return done;
}

bool EditorCell::HandleOrdinaryKey(wxKeyEvent &event) {
  if (event.ControlDown() && !event.AltDown())
    return false;

  m_isDirty = true;
  m_containsChanges = true;
  bool insertLetter = true;

  if(SelectionActive())
    SaveValue();
  else
    SaveValue(History::Action::addChar);

  wxChar keyCode;
  keyCode = event.GetUnicodeKey();

  // If we got passed a non-printable character we have to send it back to the
  // hotkey management.
  if (keyCode == WXK_NONE) {
    event.Skip();
    return false;
  }

  // It may be not too intelligent to cache all pieces of a word we arrived at
  // during typing
  if (keyCode == ' ')
    m_widths.clear();

  // if we have a selection either put parens around it (and don't write the
  // letter afterwards) or delete selection and write letter (insertLetter =
  // true).
  if (SelectionActive()) {
    auto start = SelectionLeft();
    auto end   = SelectionRight();

    switch (keyCode) {
    case '(':
      m_text = m_text.Left(start) + wxS("(") +
        m_text.Mid(start, end - start) + wxS(")") + m_text.Mid(end);
      CursorPosition(start);
      insertLetter = false;
      break;
    case '\"':
      m_text = m_text.Left(start) + wxS("\"") +
        m_text.Mid(start, end - start) + wxS("\"") + m_text.Mid(end);
      CursorPosition(start);
      insertLetter = false;
      break;
    case '{':
      m_text = m_text.Left(start) + wxS("{") +
        m_text.Mid(start, end - start) + wxS("}") + m_text.Mid(end);
      CursorPosition(start);
      insertLetter = false;
      break;
    case '[':
      m_text = m_text.Left(start) + wxS("[") +
        m_text.Mid(start, end - start) + wxS("]") + m_text.Mid(end);
      CursorPosition(start);
      insertLetter = false;
      break;
    case ')':
      m_text = m_text.Left(start) + wxS("(") +
        m_text.Mid(start, end - start) + wxS(")") + m_text.Mid(end);
      CursorPosition(end + 2);
      insertLetter = false;
      break;
    case '}':
      m_text = m_text.Left(start) + wxS("{") +
        m_text.Mid(start, end - start) + wxS("}") + m_text.Mid(end);
      CursorPosition(end + 2);
      insertLetter = false;
      break;
    case ']':
      m_text = m_text.Left(start) + wxS("[") +
        m_text.Mid(start, end - start) + wxS("]") + m_text.Mid(end);
      CursorPosition(end + 2);
      insertLetter = false;
      break;
    default: // delete selection
      m_text = m_text.Left(start) + m_text.Mid(end);
      CursorPosition(start);
      break;
    }
    ClearSelection();
    StyleText();
  } // end if (SelectionActive())

    // insert letter if we didn't insert brackets around selection
  if (insertLetter) {
    wxString chr;

    chr = event.GetUnicodeKey();

    if (event.ShiftDown())
      chr.Replace(wxS(" "), wxS("\u00a0"));

    m_text = m_text.Left(CursorPosition()) + chr +
      m_text.Mid(CursorPosition());

    CursorMove(1);

    if (m_configuration->GetMatchParens()) {
      switch (keyCode) {
      case '(':
        m_text = m_text.SubString(0, CursorPosition() - 1) + wxS(")") +
          m_text.SubString(CursorPosition(), m_text.Length());
        break;
      case '[':
        m_text = m_text.SubString(0, CursorPosition() - 1) + wxS("]") +
          m_text.SubString(CursorPosition(), m_text.Length());
        break;
      case '{':
        m_text = m_text.SubString(0, CursorPosition() - 1) + wxS("}") +
          m_text.SubString(CursorPosition(), m_text.Length());
        break;
      case '"':
        if (CursorPosition() < m_text.Length() &&
            m_text.at(CursorPosition()) == '"')
          m_text = m_text.SubString(0, CursorPosition() - 2) +
            m_text.SubString(CursorPosition(), m_text.Length());
        else
          m_text = m_text.SubString(0, CursorPosition() - 1) + wxS("\"") +
            m_text.SubString(CursorPosition(), m_text.Length());
        break;
      case ')': // jump over ')'
        if (CursorPosition() < m_text.Length() &&
            m_text.at(CursorPosition()) == ')')
          m_text = m_text.Left(CursorPosition() - 1) +
            m_text.Mid(CursorPosition());
        break;
      case ']': // jump over ']'
        if (CursorPosition() < m_text.Length() &&
            m_text.at(CursorPosition()) == ']')
          m_text = m_text.Left(CursorPosition() - 1) +
            m_text.Mid(CursorPosition());
        break;
      case '}': // jump over '}'
        if (CursorPosition() < m_text.Length() &&
            m_text.at(CursorPosition()) == '}')
          m_text = m_text.Left(CursorPosition() - 1) +
            m_text.Mid(CursorPosition());
        break;
      case '+':
        // case '-': // this could mean negative.
      case '*':
      case '/':
      case '^':
      case '=':
      case ',':
        size_t len = m_text.Length();
        if (m_configuration->GetInsertAns()) {
          // Insert an "%" before an operator that begins this cell
          if (len == 1 && CursorPosition() == 1) {
            m_text = m_text.SubString(0, CursorPosition() - 2) + wxS("%") +
              m_text.SubString(CursorPosition() - 1, m_text.Length());
            CursorMove(1);
          }

          // If this operator happens to be the first letter of a comment start
          // sign we remove the "%" again as the unability to begin a code cell
          // with a comment in the obvious way tends to surprise users.
          if ((len == 3) && (CursorPosition() == 3) &&
              (m_text.StartsWith(wxS("%/*")))) {
            m_text = m_text.SubString(CursorPosition() - 2, m_text.Length());
            CursorMove(-1);
          }
        }
        break;
      }
    }
  } // end if (insertLetter)

  StyleText();
  return true;
}

/**
 * For a given quotation mark ("), find a matching quote.
 * Since there are no nested quotes, an odd-numbered, non-escaped quote
 * is an opening quote, and an even-numbered non-escaped quote
 * is a closing quote.
 *
 * @return true if matching quotation marks were found; false otherwise
 */
bool EditorCell::FindMatchingQuotes() {
  size_t pos = 0;
  for (auto const &tok : m_tokens) {
    if ((tok.GetText().StartsWith(wxS("\""))) &&
        (tok.GetText().EndsWith(wxS("\"")))) {
      size_t tokenEnd = pos + tok.GetText().Length() - 1;
      if ((CursorPosition() == tokenEnd) ||
          (CursorPosition() == pos)) {
        m_paren1 = pos;
        m_paren2 = tokenEnd;
        return true;
      }
    }
    if (pos > CursorPosition())
      return false;
    pos += tok.GetText().Length();
  }
  return false;
}

void EditorCell::FindMatchingParens() {
  m_paren1 = m_paren2 = -1;
  if (CursorPosition() >= m_text.Length())
    return;

  wxChar charUnderCursor = m_text.at(CursorPosition());
  if (charUnderCursor == wxS('\"')) {
    FindMatchingQuotes();
    return;
  }
  if ((charUnderCursor == wxS('(')) || (charUnderCursor == wxS('[')) ||
      (charUnderCursor == wxS('{'))) {
    size_t parenLevel = 0;
    size_t pos = 0;
    for (auto const &tok : m_tokens) {
      if (pos >= CursorPosition()) {
        if ((tok.GetText().StartsWith(wxS("("))) ||
            (tok.GetText().StartsWith(wxS("["))) ||
            (tok.GetText().StartsWith(wxS("{"))))
          parenLevel++;
        else {
          if ((tok.GetText().StartsWith(wxS(")"))) ||
              (tok.GetText().StartsWith(wxS("]"))) ||
              (tok.GetText().StartsWith(wxS("}")))) {
            parenLevel--;
            if (parenLevel == 0) {
              m_paren1 = CursorPosition();
              m_paren2 = pos;
              return;
            }
          }
        }
      }
      pos += tok.GetText().Length();
    }
    return;
  }
  if ((charUnderCursor == wxS(')')) || (charUnderCursor == wxS(']')) ||
      (charUnderCursor == wxS('}'))) {
    size_t parenLevel = 0;
    size_t pos;
    if(m_text.IsEmpty())
      pos = 0;
    else
      pos = m_text.Length() - 1;
    auto const tokens = MaximaTokenizer(m_text, m_configuration).PopTokens();
    for (auto tok = tokens.rbegin(); tok != tokens.rend(); ++tok) {
      if (pos <= CursorPosition()) {
        if ((tok->GetText().StartsWith(wxS("("))) ||
            (tok->GetText().StartsWith(wxS("["))) ||
            (tok->GetText().StartsWith(wxS("{")))) {
          parenLevel--;
          if (parenLevel == 0) {
            m_paren1 = pos;
            m_paren2 = CursorPosition();
            return;
          }
        } else {
          if ((tok->GetText().StartsWith(wxS(")"))) ||
              (tok->GetText().StartsWith(wxS("]"))) ||
              (tok->GetText().StartsWith(wxS("}")))) {
            parenLevel++;
          }
        }
      }
      pos -= tok->GetText().Length();
    }
  }
}

wxString EditorCell::InterpretEscapeString(const wxString &txt) {
  auto &escCode = Configuration::GetEscCode(txt);
  if (!escCode.empty())
    return escCode;

  long int unicodeval = -1;
  if (txt.ToLong(&unicodeval, 16)) {
    if (unicodeval > 32)
      return wxUniChar(unicodeval);
    return wxS(" ");
  }
  return {};
}

void EditorCell::DeactivateCursor() {
//  EditorCell *editor = m_documentCellPointers->GetActiveCell();
//  if (editor) {
//    editor->ClearSelection();
//    editor->m_paren1 = editor->m_paren2 = -1;
//  }
  m_documentCellPointers->ClearActiveCell();
  m_documentCellPointers->ClearSelectionString();
}

bool EditorCell::ActivateCursor() {
  bool retval = false;
  if (!m_documentCellPointers->GetActiveCell())
    DeactivateCursor();

  // Sanity check: Ensure cursor is within bounds
  if (m_selectionStart > m_text.Length())
    m_selectionStart = m_text.Length();
  if (m_selectionEnd > m_text.Length())
    m_selectionEnd = m_text.Length();

  SaveValue();
  m_displayCaret = true;
  m_hasFocus = true;
  m_documentCellPointers->SetActiveCell(this);

  ClearSelection();
  m_paren1 = m_paren2 = -1;

  // upon activation unhide the parent groupcell
  if (FirstLineOnlyEditor()) {
    GroupCell *group = GetGroup();
    if (group) {
      group->Hide(false);
      retval = true;
    }
  }
  if (GetType() == MC_TYPE_INPUT)
    FindMatchingParens();
  return retval;
}

bool EditorCell::AddEnding() {
  // Lisp cells don't require a maxima line ending
  if (m_configuration->InLispMode())
    return false;

  // Cells that aren't code cells don't require a maxima line ending.
  if (GetType() != MC_TYPE_INPUT)
    return false;

  bool endingNeeded = true;

  auto IsWhitespace = [](const wxString& s) {
    for (size_t i = 0; i < s.length(); ++i) {
      wxUniChar c = s[i];
      if (!wxIsspace(c) && c != wxS('\u00a0') && c != wxS('\u2028') && c != wxS('\u2029'))
        return false;
    }
    return true;
  };

  for (auto const &tok : GetAllTokens()) {
    TextStyle itemStyle = tok.GetTextStyle();
    if ((itemStyle == TS_CODE_ENDOFLINE) || (itemStyle == TS_CODE_LISP)) {
      endingNeeded = false;
    } else {
      if (!IsWhitespace(tok.GetText()) && (itemStyle != TS_CODE_COMMENT))
        endingNeeded = true;
    }
  }

  if (endingNeeded) {
    wxString trimmed = m_text;
    trimmed.Replace(wxS("\u00a0"), " ");
    trimmed.Trim(true);
    if (trimmed.EndsWith(wxS(";")) || trimmed.EndsWith(wxS("$"))) {
      endingNeeded = false;
    }
  }

  if (endingNeeded) {
    m_text += wxS(";");
      m_paren1 = m_paren2 = -1;
      m_width.Invalidate();
    StyleText();
    return true;
  }
  return false;
}

//
// lines and columns are counted from zero
// position of caret is pos if caret is just before the character
//   at position pos in m_text.
//
// Maps a content offset to a display (column, line) pair. Display lines are
// bounded by hard newlines (a '\n' char in m_text) AND by soft breaks (kept in
// the m_softBreaks side table, never in m_text). A soft break is zero-width -
// it sits *between* two characters - so it advances the line without consuming
// a character, unlike the '\n' it stands in for.
void EditorCell::PositionToXY(size_t position, size_t *x, size_t *y) {
  size_t col = 0, lin = 0;
  size_t pos = 0;

  wxString::const_iterator it = m_text.begin();
  while ((pos < position) && (it < m_text.end())) {
    if (IsSoftBreakBefore(pos)) {
      col = 0;
      lin++;
    }
    if (*it == '\n') {
      col = 0;
      lin++;
    } else
      col++;

    ++it;
    ++pos;
  }
  // A caret sitting exactly on a soft break belongs to the following line.
  if (IsSoftBreakBefore(position)) {
    col = 0;
    lin++;
  }

  *x = col;
  *y = lin;
}

size_t EditorCell::XYToPosition(size_t x, size_t y) {
  size_t lin = 0, pos = 0;
  const size_t len = m_text.Length();

  // Phase 1: walk to the first content offset of display line y.
  while (lin < y) {
    if (IsSoftBreakBefore(pos)) {
      // Zero-width soft break: line y begins right here, no char consumed.
      if (++lin == y)
        break;
    }
    if (pos >= len)
      break;
    if (m_text[pos] == '\n') {
      // Hard newline: consume it; the next line starts just past it.
      ++pos;
      if (++lin == y)
        break;
      continue;
    }
    ++pos;
  }

  // Phase 2: advance x columns within line y, stopping at the line's end.
  const size_t lineStart = pos;
  size_t col = 0;
  while ((pos < len) && (col < x)) {
    // The break that starts this very line (at lineStart) must not end it.
    if ((pos != lineStart) && IsSoftBreakBefore(pos))
      break;
    if (m_text[pos] == '\n')
      break;
    ++pos;
    ++col;
  }

  return pos;
}

wxPoint EditorCell::PositionToPoint(size_t pos) {
  SetFont(m_configuration->GetRecalcDC());

  wxCoord x = m_currentPoint.x, y = m_currentPoint.y;

  if ((x < 0) || (y < 0))
    return wxDefaultPosition;

  size_t cX, cY;
  PositionToXY(pos, &cX, &cY);

  wxCoord mixedOffset;
  if (LineIsMixedDirection(cY) && MixedDirectionOffset(cY, pos, &mixedOffset)) {
    // The real bidi algorithm places this exactly - the same computation
    // MarkSelection() already uses (see MixedDirectionOffset()) - superseding
    // the single-direction mirror/plain-width choice below, which only knows
    // whether the *whole line* is one direction.
    x += mixedOffset;
  } else {
    const wxCoord width = GetLineWidth(cY, cX);

    // A right-to-left line is drawn in the reverse of the order it is stored
    // in, so the text before a position is drawn *after* it: the caret
    // belongs that far in from the line's right end rather than from its
    // left. Mirroring the measurement is exact as long as the line is wholly
    // one direction, which is the case LineIsRightToLeft() answers for.
    if (LineIsRightToLeft(cY) && !LineIsMixedDirection(cY))
      x += GetLineWidth(cY, LineLength(cY)) - width;
    else
      x += width;
  }

  // The caret has to follow the text when a right-to-left line is set flush
  // right - the same shift Draw() applies to that line.
  x += RightToLeftLineOffset(cY);
  y += m_charHeight * cY;

  return wxPoint(x, y);
}

void EditorCell::SelectPointText(const wxPoint point) {
  SetFont(m_configuration->GetRecalcDC());
  wxPoint posInCell(point);

  wxASSERT_MSG(m_currentPoint.x >= 0, _("Bug: x position of cell is unknown!"));
  wxASSERT_MSG(m_currentPoint.y >= 0, _("Bug: y position of cell is unknown!"));
  posInCell -= m_currentPoint;
  //  posInCell -= wxPoint(m_fontSize, 2);
  posInCell.y -= m_center;

  size_t lin = static_cast<size_t>(posInCell.y) / m_charHeight + 1;
  if (posInCell.y < 0)
    lin = 0;
  size_t lineStart = XYToPosition(0, lin);

  auto pos = lineStart;
  // Find the text snippet the line we search for begins with
  size_t currentLine = 1;
  wxCoord indentPixels = 0;
  std::vector<StyledText>::const_iterator textSnippet;
  for (textSnippet = m_styledText.begin();
       ((textSnippet != m_styledText.end()) && (currentLine <= lin));
       ++textSnippet) {
    if ((textSnippet->GetText() == '\n') || (textSnippet->GetText() == '\r')) {
      indentPixels = textSnippet->GetIndentPixels();
      currentLine++;
    }
  }
  // Handle indentation: a wrapped continuation line is drawn shifted right by
  // its indentPixels (see Draw), so a click's x must be measured from there.
  // This applies to code and text cells alike. A right-to-left line is shifted
  // right once more, to sit flush with the widest one, so take that off too --
  // otherwise a click lands on the wrong character.
  posInCell.x -= indentPixels;
  posInCell.x -= RightToLeftLineOffset(lin > 0 ? lin - 1 : 0);

  if (GetType() == MC_TYPE_INPUT) {
    // Code cell

    wxCoord xpos = 0;
    // Find the text snippet the cursor is in
    while ((textSnippet != m_styledText.end()) && (xpos < posInCell.x)) {
      wxString txt = textSnippet->GetText();

      if ((txt == wxS("\n")) || (txt == wxS("\r")))
        break;

      if (txt == wxS("\t")) {
        // A tab's width is position-dependent, so it can't be measured via
        // GetTextSize(); treat the whole tab as one unit and round at its own
        // midpoint, mirroring how the general case rounds at half its first
        // character's width.
        const wxCoord stop = NextTabStop(xpos);
        if (xpos + (stop - xpos) / 2 < posInCell.x) {
          xpos = stop;
          pos += 1;
        } else
          break;
        ++textSnippet;
        continue;
      }

      wxCoord firstCharWidth = GetTextSize(txt.Left(1)).GetWidth();
      wxCoord w = GetTextSize(txt).GetWidth();
      if (xpos + w + firstCharWidth / 2 < posInCell.x) {
        xpos += w;
        pos += txt.Length();
      } else
        break;

      ++textSnippet;
    }

    wxCoord lastwidth = 0;
    wxString snippet;
    if (textSnippet != m_styledText.end())
      snippet = textSnippet->GetText();

    if (snippet == wxS("\t")) {
      // Same whole-unit rounding as the coarse scan above: land before or
      // after the tab depending on which side of its midpoint the click fell.
      const wxCoord stop = NextTabStop(xpos);
      if (xpos + (stop - xpos) / 2 < posInCell.x)
        pos++;
    } else {
      lastwidth = GetTextSize(snippet.Left(1)).GetWidth();
      lastwidth = -lastwidth;

      // Now determine which char inside this text snippet the cursor is at
      if ((snippet != wxS("\r")) && (snippet != wxS("\n"))) {
        for (size_t i = 0; i < snippet.Length(); i++) {
          wxCoord width = GetTextSize(snippet.Left(i)).GetWidth();
          if (xpos + width + (width - lastwidth) / 2 < posInCell.x)
            pos++;
          else
            break;
          lastwidth = width;
        }
      }
    }
    m_displayCaret = true;
    FindMatchingParens();
    // The line that now follows is pure paranoia.
    pos = std::min(pos, m_text.Length());
  } else {
    // Text cell: search for the position on this display line whose caret -
    // as PositionToPoint() places it, the same source of truth Draw() and
    // MarkSelection() use - is closest to the click, instead of re-deriving
    // the mapping by hand. A per-character forward scan comparing against a
    // monotonically growing substring width (what used to be here) assumes
    // stored order is drawn order, which only holds for left-to-right text:
    // on a right-to-left line it resolved a click to the *mirror image* of
    // the right position, and a mixed-direction line fares no better.
    auto distanceTo = [&](size_t candidate) {
      const wxCoord x = PositionToPoint(candidate).x;
      return (x > point.x) ? (x - point.x) : (point.x - x);
    };

    const size_t lineEnd = lineStart + LineLength(lin);
    size_t best = lineStart;
    wxCoord bestDist = distanceTo(lineStart);
    for (size_t candidate = lineStart + 1; candidate <= lineEnd; candidate++) {
      const wxCoord dist = distanceTo(candidate);
      if (dist < bestDist) {
        bestDist = dist;
        best = candidate;
      }
    }
    pos = best;

    m_displayCaret = true;
  }
  CursorPosition(pos);
}

void EditorCell::SelectRectText(const wxPoint one, const wxPoint two) {
  SelectPointText(one);
  size_t start = CursorPosition();
  SelectPointText(two);
  m_paren2 = m_paren1 = -1;
  SelectionStart(start);
}

// IsPointInSelection
// Return true if coordinates "point" fall into selection
// If they don't or there is no selection it returns false
bool EditorCell::IsPointInSelection(wxPoint point) {
  if ((!SelectionActive()) || !IsActive())
    return false;

  wxRect rect = GetRect();
  if (!rect.Contains(point))
    return false;

  wxString text = m_text;
  SetFont(m_configuration->GetRecalcDC());
  // Determine the line the point would be in
  wxPoint posInCell(point);
  posInCell.x -= m_currentPoint.x - 2;
  posInCell.y -= m_currentPoint.y - 2 - m_center;
  size_t lin = posInCell.y / m_charHeight;
  size_t lineStart = XYToPosition(0, lin);
  size_t positionOfCaret = lineStart;

  // Find the text snippet the line we search for begins with for determining
  // the indentation needed.
  size_t currentLine = 1;
  wxCoord indentPixels = 0;

  for (const auto &textSnippet : m_styledText) {
    if (currentLine >= lin)
      break;
    if ((textSnippet.GetText() == '\n') || (textSnippet.GetText() == '\r')) {
      indentPixels = textSnippet.GetIndentPixels();
      currentLine++;
    }
  }

  // Handle indentation
  posInCell.x -= indentPixels;

  wxString::const_iterator it = m_text.begin() + positionOfCaret;
  // Stop at the hard newline OR the soft break that ends this display line;
  // the soft break that STARTED it (at lineStart) must not end it immediately.
  while (it != m_text.end() && *it != '\n' &&
         !((positionOfCaret != lineStart) && IsSoftBreakBefore(positionOfCaret))) {
    wxCoord width;
    wxString strng = m_text.SubString(lineStart, positionOfCaret);
    width = GetTextSize(strng).GetWidth();
    if (width > posInCell.x)
      break;
    positionOfCaret++;
    ++it;
  }
  positionOfCaret = std::min(positionOfCaret, text.Length());
  return ((SelectionLeft() <= positionOfCaret) &&
           (positionOfCaret < SelectionRight()));
}

wxString EditorCell::DivideAtCaret() {
  wxString original = m_text;
  m_containsChanges = true;
  wxString newText = m_text.Left(CursorPosition());

  // Remove an eventual newline from the end of the old cell
  // that would appear if the cell is divided at the beginning of a line.
  if (newText.Length() > 0) {
    // Search for the end of whitespace at the end of the new cell
    size_t whiteSpaceEnd = newText.Length() - 1;
    wxString::const_iterator it = newText.begin() + whiteSpaceEnd;
    while (it < newText.end() && (*it == wxS(' ') || *it == wxS('\t'))) {
      whiteSpaceEnd++;
      ++it;
    }

    if (it < newText.end() && (*it == wxS('\n') || *it == wxS('\r')))
      newText = newText.Left(whiteSpaceEnd);
  }

  SetValue(newText);
  wxString retval = original.Mid(CursorPosition());
  // Remove an eventual newline from the beginning of a new cell
  // that would appear if the cell is divided at the end of a line.
  if (retval.Length() > 0) {
    // Search for the end of whitespace at the beginning of the new cell
    size_t whiteSpaceEnd = 0;
    wxString::const_iterator it = retval.begin();
    while (it < retval.end() && (*it == wxS(' ') || *it == wxS('\t'))) {
      whiteSpaceEnd++;
      ++it;
    }

    if (it < retval.end() && (*it == wxS('\n') || *it == wxS('\r')))
      retval = retval.Mid(whiteSpaceEnd + 1);
    m_containsChanges = true;
  }
  return retval;
}

void EditorCell::UpdateSelectionString() {
  wxString oldSelectionString = m_documentCellPointers->GetSelectionString();
  wxString selectionString =
    m_text.Mid(SelectionLeft(),
               SelectionRight() - SelectionLeft());
  m_documentCellPointers->SetSelectionString(selectionString);
  if(selectionString != oldSelectionString)
    m_selectionChanged = true;
}

void EditorCell::CommentSelection() {
  if (!SelectionActive())
    return;
  m_containsChanges = true;
  m_isDirty = true;
  SetValue(m_text.Left(SelectionStart()) + wxS("/*") +
           m_text.Mid(SelectionStart(), SelectionEnd() - SelectionStart()) + wxS("*/") +
           m_text.Mid(SelectionEnd()));
  CursorPosition(std::min(SelectionEnd() + 4, m_text.Length()));
}

wxString EditorCell::GetWordUnderCaret() {
  size_t start = CursorPosition();
  if (start >= m_text.Length())
    start = m_text.Length();

  wxString retval;
  size_t pos = 0;
  wxString::const_iterator it;
  for (it = m_text.begin(); it < m_text.end(); ++it) {
    if (!wxIsalnum(*it) && !(*it == '\\') && !(*it == '_') && !(*it == '&') &&
        !(*it == '%') && !(*it == '?')) {
      if (pos >= start)
        break;
      else
        retval.Clear();
    } else
      retval += *it;

    pos++;

    if (*it == '\\') {
      ++it;
      if (it == m_text.end())
        break;
      retval += *it;
      pos++;
    }
  }
  return retval;
}

/***
 * SelectWordUnderCaret
 * - called from MathCtrl::OnDoubleClick, MathCtrl::Autocomplete and
 * wxMaxima::HelpMenu Selects word under cursor (aA-zZ, 0-9, %, _, count) or the
 * inside of brackets using m_paren1 and m_paren2 if available and selectParens
 * is true. Returns the selected string if selected a word successfully - used
 * for F1 help and MathCtrl::Autocomplete.
 */
wxString EditorCell::SelectWordUnderCaret(bool WXUNUSED(selectParens),
                                          bool toRight,
                                          bool includeDoubleQuotes) {
  size_t start = 0;
  size_t pos = 0;
  for (wxString::const_iterator it = m_text.begin(); it != m_text.end(); ++it) {
    if (*it == '\\') {
      pos++;
      ++it;
      if (it == m_text.end())
        break;
      pos++;
      continue;
    }
    if (!wxIsalnum(*it) && !(*it == '\\') && !(*it == '_') && !(*it == '?') &&
        !(*it == '%') && !((*it == '\"') && includeDoubleQuotes)) {
      // !!toRight is 0, if toRight is false or guaranteed to be 1, if toRight
      // !! toRight either is 0 or 1.
      if (pos >= CursorPosition() + !!toRight)
        break;
      else
        start = pos + 1;
    }
    pos++;
  }
  if (pos > 0)
    SetSelection(start, pos);

  if (pos > 0 && start != pos)
    return m_documentCellPointers->GetSelectionString();
  else
    return wxS("%");
}

bool EditorCell::CopyToClipboard() const {
  wxASSERT_MSG(!wxTheClipboard->IsOpened(),
               _("Bug: The clipboard is already opened"));
  auto start = SelectionLeft();
  size_t end;
  if(SelectionRight() > 0)
    end = SelectionRight() - 1;
  else
    end = 0;
  if (end > m_text.Length())
    end = m_text.Length();
  wxString s = m_text.SubString(start, end);
  // Copying non-breakable spaces in code to external applications is likely to
  // cause problems
  if (GetType() == MC_TYPE_INPUT)
    s.Replace(wxS("\u00a0"), wxS(" "));
  if (!s.IsEmpty() && (wxTheClipboard->Open())) {
    if (!wxTheClipboard->SetData(new wxTextDataObject(s))) {
      wxLogMessage(_("Cannot put the copied text on the clipboard (1st try)"));
      wxMicroSleep(500000);
      if (!wxTheClipboard->SetData(new wxTextDataObject(s))) {
        wxLogMessage(
                     _("Cannot put the copied text on the clipboard (2nd try)"));
        wxMicroSleep(500000);
        if (!wxTheClipboard->SetData(new wxTextDataObject(s)))
          wxLogMessage(_("Cannot put the copied text on the clipboard"));
      }
    }
    wxTheClipboard->Close();
    return true;
  } else
    return false;
}

bool EditorCell::CutToClipboard() {
  if (!SelectionActive())
    return false;

  SaveValue();
  m_containsChanges = true;
  if (!CopyToClipboard())
    return false;

  auto start = SelectionLeft();
  auto end = SelectionRight();
  CursorPosition(start);

  // We cannot use SetValue() here, since SetValue() tends to move the cursor.
  m_text = m_text.Left(start) + m_text.Mid(end);
  StyleText();

  ClearSelection();
  m_paren1 = m_paren2 = -1;
  m_width.Invalidate();
  m_height.Invalidate();
  m_center.Invalidate();

  return true;
}

void EditorCell::InsertText(wxString text) {
  SaveValue();
  m_containsChanges = true;

  text = NormalizeLineEndings(text);

  ReplaceSelection(GetSelectionString(), text);

  if (GetType() == MC_TYPE_INPUT)
    FindMatchingParens();

  m_text.Replace(wxS("\u2028"), "\n");
  m_text.Replace(wxS("\u2029"), "\n");

  //  m_width.Invalidate();
  m_height.Invalidate();
  m_center.Invalidate();
  //  InvalidateMaxDrop();
  StyleText();
}

void EditorCell::PasteFromClipboard(const bool primary) {
  wxTheClipboard->UsePrimarySelection(primary);
  wxASSERT_MSG(
               wxTheClipboard->IsOpened(),
               _("Bug: The clipboard isn't open on pasting into an editor cell"));
  if (wxTheClipboard->IsSupported(wxDF_TEXT) ||
      wxTheClipboard->IsSupported(wxDF_UNICODETEXT)) {
    wxTextDataObject obj;
    wxTheClipboard->GetData(obj);
    wxString text = obj.GetText();
    // Normalize external line endings to the hard break '\n': Windows uses
    // "\r\n" and classic macOS a lone '\r'. Internally '\r' is wxMaxima's *soft*
    // word-wrap break, so pasted text must not smuggle one in (it would freeze a
    // line break into the content). Done here, on the incoming clipboard string
    // only, so existing soft breaks already in m_text are untouched.
    text.Replace(wxS("\r\n"), wxS("\n"));
    text.Replace(wxS("\r"), wxS("\n"));
    InsertText(text);
    m_containsChanges = true;
    StyleText();
  }
  if (primary)
    wxTheClipboard->UsePrimarySelection(false);
}

std::vector<wxCoord> EditorCell::RightToLeftLineOffsets() {
  std::vector<wxCoord> offsets;
  if (!m_configuration->RightToLeftDocument())
    return offsets;

  // Prose follows the document's direction; code does not. "مقدار : 5;" is
  // still Maxima syntax and reads left to right even though the variable's own
  // name is Farsi -- the same reason an equation is never mirrored. So an input
  // or prompt cell keeps its left margin in a right-to-left document, and only
  // text, title and section cells move.
  if (IsCodeType(m_type))
    return offsets;

  // Measure every line, then shift each one right by whatever is left over of
  // the width available to it: the result is prose set flush right with a
  // ragged left margin, which is what a right-to-left script wants.
  //
  // The reference is Configuration::GetLineWidth() -- the viewport less
  // everything that sits left of a text cell's text and less the right-hand
  // margin, i.e. exactly the width this cell's lines were wrapped into, and
  // exactly the width available from the point Draw() starts at. Aligning to
  // the widest line *within the cell* instead would be wrong: a single-line
  // paragraph is its own widest line and so would never move at all.
  wxDC *const dc = m_configuration->GetRecalcDC();
  SetFont(dc);
  const wxCoord available = m_configuration->GetLineWidth();

  std::vector<wxCoord> lineWidths;
  wxCoord lineWidth = 0;
  for (const auto &textSnippet : m_styledText) {
    if ((textSnippet.GetText() == wxS("\n")) ||
        (textSnippet.GetText() == wxS("\r"))) {
      lineWidths.push_back(lineWidth);
      lineWidth = textSnippet.GetIndentPixels();
    } else if (textSnippet.SizeKnown())
      lineWidth += textSnippet.GetWidth();
    else {
      wxCoord width, height;
      dc->GetTextExtent(textSnippet.GetText(), &width, &height);
      lineWidth += width;
    }
  }
  lineWidths.push_back(lineWidth);

  // A line that is wider than the space available (an unbreakable word, say)
  // stays where it is rather than being pushed off to the left.
  offsets.reserve(lineWidths.size());
  for (const wxCoord width : lineWidths)
    offsets.push_back(std::max(available - width, 0));
  return offsets;
}

wxCoord EditorCell::RightToLeftLineOffset(size_t line) {
  const std::vector<wxCoord> offsets = RightToLeftLineOffsets();
  if (line < offsets.size())
    return offsets.at(line);
  return 0;
}

bool EditorCell::IsStrongRightToLeft(wxUniChar c) {
  const auto u = static_cast<wxUint32>(c);
  return
    // Hebrew, and the alphabetic presentation forms that follow it
    ((u >= 0x0590) && (u <= 0x05FF)) || ((u >= 0xFB1D) && (u <= 0xFB4F)) ||
    // Arabic, including the Farsi and Urdu letters, plus its supplements
    ((u >= 0x0600) && (u <= 0x06FF)) || ((u >= 0x0750) && (u <= 0x077F)) ||
    ((u >= 0x08A0) && (u <= 0x08FF)) ||
    // Arabic presentation forms A and B
    ((u >= 0xFB50) && (u <= 0xFDFF)) || ((u >= 0xFE70) && (u <= 0xFEFF)) ||
    // Syriac, Thaana, N'Ko, Samaritan, Mandaic
    ((u >= 0x0700) && (u <= 0x074F)) || ((u >= 0x0780) && (u <= 0x07BF)) ||
    ((u >= 0x07C0) && (u <= 0x07FF)) || ((u >= 0x0800) && (u <= 0x083F)) ||
    ((u >= 0x0840) && (u <= 0x085F));
}

bool EditorCell::IsStrongLeftToRight(wxUniChar c) {
  // Everything a Latin, Greek or Cyrillic script writes with, which is as much
  // as this needs to tell apart from the right-to-left blocks above. Digits are
  // deliberately not here: they take their direction from their surroundings.
  return wxIsalpha(c) && !IsStrongRightToLeft(c);
}

bool EditorCell::LineIsRightToLeft(size_t line) {
  const size_t start = XYToPosition(0, line);
  for (size_t pos = start; pos < m_text.Length(); pos++) {
    const wxUniChar c = m_text.at(pos);
    if (c == wxS('\n'))
      break;
    if (IsStrongRightToLeft(c))
      return true;
    if (IsStrongLeftToRight(c))
      return false;
  }
  // Nothing strong either way: follow the document.
  return m_configuration->RightToLeftDocument();
}

size_t EditorCell::LineLength(size_t line) {
  const size_t start = XYToPosition(0, line);
  size_t length = 0;
  while ((start + length < m_text.Length()) &&
         (m_text.at(start + length) != wxS('\n')))
    length++;
  return length;
}

bool EditorCell::LineIsMixedDirection(size_t line) {
  const size_t start = XYToPosition(0, line);
  bool sawRightToLeft = false;
  bool sawLeftToRight = false;
  for (size_t pos = start; pos < m_text.Length(); pos++) {
    const wxUniChar c = m_text.at(pos);
    if (c == wxS('\n'))
      break;
    sawRightToLeft = sawRightToLeft || IsStrongRightToLeft(c);
    sawLeftToRight = sawLeftToRight || IsStrongLeftToRight(c);
  }
  return sawRightToLeft && sawLeftToRight;
}

bool EditorCell::GetLineBidiRuns(size_t line, std::vector<BidiRun> *runs) {
  if (!Bidi::IsAvailable())
    return false;

  const size_t lineStart = XYToPosition(0, line);
  const size_t lineLen = LineLength(line);
  if (lineLen == 0)
    return false;

  const wxString lineText = m_text.SubString(lineStart, lineStart + lineLen - 1);
  *runs = Bidi::GetRuns(lineText, m_configuration->RightToLeftDocument());
  // Bidi::GetRuns() only ever sees this one line's text, so its runs are
  // relative to it; shift them to absolute m_text positions so every caller
  // here can compare them against the positions it already works with.
  for (auto &r : *runs) {
    r.logicalStart += lineStart;
    r.logicalEnd += lineStart;
  }
  return true;
}

bool EditorCell::MixedDirectionOffset(size_t line, size_t position, wxCoord *offset) {
  std::vector<BidiRun> runs;
  if (!GetLineBidiRuns(line, &runs))
    return false;

  const BidiRun *run = nullptr;
  for (const auto &r : runs)
    if ((position >= r.logicalStart) && (position <= r.logicalEnd)) {
      run = &r;
      break;
    }
  if (!run)
    return false;

  // A wrapped continuation line starts indented (see Draw()/GetLineWidth());
  // the run composition below measures from where the *text* starts, so the
  // indent has to be added back on top to land in the same coordinate space
  // as GetLineWidth() and PositionToPoint()'s other callers.
  wxCoord x = GetLineWidth(line, 0);

  // Runs that come before this one on screen, in full: they sit to this
  // run's left regardless of their own direction.
  for (const auto &r : runs) {
    if (&r == run)
      break;
    x += MeasureTextWidth(x, m_text.SubString(r.logicalStart, r.logicalEnd - 1));
  }

  // Within the run itself: a left-to-right run measures normally, from its
  // own start; a right-to-left one is drawn back to front, so a position's
  // distance from the run's *left* edge is the width of what comes *after*
  // it in logical order - the same reasoning SelectionRunLeft() uses for a
  // wholly-right-to-left line, just local to this one run instead of the
  // whole line.
  if (run->rightToLeft) {
    if (position < run->logicalEnd)
      x += MeasureTextWidth(x, m_text.SubString(position, run->logicalEnd - 1));
  } else {
    if (position > run->logicalStart)
      x += MeasureTextWidth(x, m_text.SubString(run->logicalStart, position - 1));
  }
  *offset = x;
  return true;
}

wxCoord EditorCell::GetLineWidth(size_t line, size_t pos) {
  // Find the text snippet the line we search for begins with for determining
  // the indentation needed.
  size_t currentLine = 1;
  wxCoord indentPixels = 0;
  SetFont(m_configuration->GetRecalcDC());

  // Determine how many pixels the line is indented.
  for (const auto &textSnippet : m_styledText) {
    if (currentLine > line)
      break;
    if ((textSnippet.GetText() == '\n') || (textSnippet.GetText() == '\r')) {
      indentPixels = textSnippet.GetIndentPixels();
      currentLine++;
    }
  }

  // If the caller wants to know the position of the first character we can
  // already return a number
  if (pos == 0) {
    return indentPixels;
  }

  // TODO: Are we sure that this for loop does generate a result the last one
  // didn't calculate?
  size_t lin = 0;
  std::vector<StyledText>::const_iterator textSnippet;
  for (textSnippet = m_styledText.begin();
       (textSnippet < m_styledText.end()) && (lin < line); ++textSnippet) {
    wxString text = textSnippet->GetText();
    if ((text.Right(1) == '\n') || (text.Right(1) == '\r'))
      lin++;
  }

  // Handle the case that the caller wants to know a position beyond the
  // end of the text
  if (lin < line)
    return 0;

  // Step through the text snippets before the cursor in the current line
  // and add up their lengths
  wxCoord lineWidth = indentPixels;
  wxString snippet;
  for (; textSnippet < m_styledText.end(); ++textSnippet) {
    snippet = textSnippet->GetText();
    if(snippet.Length() <= pos)
      {
        pos -= snippet.Length();
        // A tab is always its own isolated, single-character snippet (see
        // MaximaTokenizer), so this is the "fully consumed" case for it; its
        // width is position-dependent, hence NextTabStop() rather than a
        // GetTextSize() lookup.
        lineWidth = (snippet == wxS("\t")) ? NextTabStop(lineWidth)
                                            : lineWidth + GetTextSize(snippet).GetWidth();
      }
    else
      {
        // pos < snippet.Length() here; for a lone-tab snippet (length 1) the
        // only way to land here is pos == 0, where Left(0) is already
        // correctly "" / width 0, so no tab special-case is needed.
        wxString partialSnippet = snippet.Left(pos);
        wxCoord snippetWidth = GetTextSize(partialSnippet).GetWidth();
        lineWidth += snippetWidth;
        break;
      }
  }

  return std::max(lineWidth, (wxCoord)m_configuration->GetCursorWidth());
}

bool EditorCell::History::AddState(EditorCell::History::HistoryEntry entry, Action action)
{
  if((m_lastAction == action) && (action != any))
    return false;
  m_lastAction = action;

  if(!m_history.empty())
    {
      if(m_history.back().GetText() == entry.GetText())
        return false;
    }

  if(m_historyPosition < m_history.size())
    {
      // If we add a history item and not are at the end of history then we want to
      // erase the "now future" history first. Or find out where in the history we are.

      for(ptrdiff_t i = static_cast<ptrdiff_t>(m_history.size()) - 1;
          i >= static_cast<ptrdiff_t>(m_historyPosition); --i)
        {
          if(m_history.at(static_cast<size_t>(i)).GetText() == entry.GetText())
            {
              m_historyPosition = static_cast<size_t>(i);
              return false;
            }
        }
      while(m_historyPosition < m_history.size())
        m_history.pop_back();
    }

  m_history.push_back(entry);
  m_historyPosition = m_history.size();

  return true;
}
bool EditorCell::History::AddState(const wxString &text, long long selStart, long long selEnd,
                                   Action action)
{
  return AddState(EditorCell::History::HistoryEntry(text, selStart, selEnd), action);
}

bool EditorCell::History::Undo()
{
  if(CanUndo())
    {
      m_historyPosition--;
      return true;
    }
  else
    return false;
}
bool EditorCell::History::Redo()
{
  if(CanRedo())
    {
      m_historyPosition++;
      return true;
    }
  return false;
}
bool EditorCell::History::CanUndo() const {return m_historyPosition > 0;}
bool EditorCell::History::CanRedo() const {return m_historyPosition + 1 < m_history.size();}
EditorCell::History::HistoryEntry EditorCell::History::GetState() const {
  // After AddState() m_historyPosition == m_history.size() (one past the last
  // entry), and on a fresh cell the history is empty. Never index past the end:
  // a caller that asks for the state with nothing to restore gets the most
  // recent entry (or a default-constructed one) instead of an out-of-range throw.
  if(m_history.empty())
    return HistoryEntry();
  size_t idx = m_historyPosition;
  if(idx >= m_history.size())
    idx = m_history.size() - 1;
  return m_history.at(idx);}
void EditorCell::History::ClearUndoBuffer() {
  m_history.clear();
  m_historyPosition = 0;}

void EditorCell::SetState(const EditorCell::History::HistoryEntry &state) {
  m_text = state.GetText();
  StyleText();
  m_paren1 = m_paren2 = -1;
  m_isDirty = true;
  m_width.Invalidate();
  m_height.Invalidate();
  m_center.Invalidate();
  SetSelection(state.SelectionStart(), state.SelectionEnd());
}

bool EditorCell::IsActive() const {
  return this == m_documentCellPointers->GetActiveCell();
}

void EditorCell::Undo() {
  // Save the value before issuing the first undo so we can undo that undo, if we want.
  if(!m_history.CanRedo())
      SaveValue();

  // Now actually undo the last change.
  m_history.Undo();

  if(m_history.GetState().GetText() == GetValue())
    m_history.Undo();

  // We cannot use SetValue() here, since SetValue() tends to move the cursor.
  SetState(m_history.GetState());
}

void EditorCell::Redo() {
  // Nothing to redo (e.g. right after typing, or on a fresh cell): don't touch
  // the state. Without this guard the old code still called GetState(), which
  // indexed one past the end of the history vector.
  if(!m_history.CanRedo())
    return;
  m_history.Redo();
  // We cannot use SetValue() here, since SetValue() tends to move the cursor.
  SetState(m_history.GetState());
}

void EditorCell::SaveValue(History::Action action) {
  m_history.AddState(GetValue(), SelectionStart(), SelectionEnd(), action);
}

bool EditorCell::BreakAfterOperator(const wxString &token,
                                    const wxString &prevToken,
                                    const wxString &nextToken) {
  // '*' is shown as U+00B7 and '-' as U+2212 when "change asterisk" is on, so
  // match both the raw and the display forms.
  auto isStar = [](const wxString &t) {
    return (t == wxS("*")) || (t == wxS("·"));
  };
  if (isStar(token)) {
    // Never split the "**" power operator (two adjacent '*' tokens): not
    // between the stars, and not right after the second one.
    if (isStar(prevToken) || isStar(nextToken))
      return false;
    return true;
  }
  return (token == wxS("+")) || (token == wxS("-")) || (token == wxS("−")) ||
         (token == wxS("/")) || (token == wxS("(")) || (token == wxS(")")) ||
         (token == wxS(";")) || (token == wxS("$"));
}

void EditorCell::HandleSoftLineBreaks_Code(SoftBreakCandidate &candidate,
                                           wxCoord &lineWidth,
                                           const wxString &token,
                                           wxCoord &indentationPixels) const {
  // If we don't want to autowrap code we don't do nothing here.
  if (!m_configuration->GetAutoWrapCode())
    return;

  // A folded cell shows only a one-line preview; wrapping it would place a
  // soft break in text the user cannot currently see.
  if (FirstLineOnlyEditor())
    return;

  SetFont(m_configuration->GetRecalcDC());

  //  Does the line extend too much to the right to fit on the screen /
  //  to be easy to read?
  wxCoord width = GetTextSize(token).GetWidth();
  lineWidth += width;

  if ((lineWidth + indentationPixels < m_configuration->GetLineWidth()) ||
      !candidate.valid)
    return;

  wxCoord charWidth = GetTextSize(" ").GetWidth();
  wxCoord newIndent =
    charWidth * static_cast<wxCoord>(GetIndentDepth(m_text, candidate.textPos));
  // Progress guarantee: every continuation line keeps at least half the
  // worksheet width for content. Unclamped, a deeply nested line's
  // continuation indentation could reach or exceed the line width, making the
  // wrapped line as wide as the unwrapped one - the "wrapping made the line
  // longer" failure that got this feature disabled.
  indentationPixels =
    wxMin(newIndent, static_cast<wxCoord>(m_configuration->GetLineWidth()) / 2);
  // The wrap condition above counted this token in lineWidth; the token now
  // starts the continuation line, so reset lineWidth to just its width (adding
  // indentationPixels here as well would double-count and wrap too early).
  lineWidth = width;

  // Record the break OUTSIDE the content. The side-table offset is the start of
  // the continuation line; StyleText appends breaks left to right, keeping
  // m_softBreaks ascending for IsSoftBreakBefore()'s binary search.
  m_softBreaks.push_back(candidate.textPos);
  if (candidate.insert) {
    // Operator break: a zero-width "\r" marker goes right after the operator.
    if (candidate.snippetIdx <= m_styledText.size())
      m_styledText.insert(m_styledText.begin() + candidate.snippetIdx,
                          StyledText(wxS("\r"), GetTextStyle(), indentationPixels));
  } else {
    // Space break: the space's render snippet becomes the "\r" marker in place,
    // standing in for the consumed space one-for-one so m_text and m_styledText
    // stay in per-line lockstep.
    if ((candidate.snippetIdx < m_styledText.size()) &&
        (m_styledText[candidate.snippetIdx].GetText() == wxS(" "))) {
      m_styledText[candidate.snippetIdx].SetText(wxS("\r"));
      m_styledText[candidate.snippetIdx].SetIndentation(indentationPixels);
    }
  }
  candidate.valid = false;
}

void EditorCell::StyleTextCode() const {
  // We have to style code
  SoftBreakCandidate candidate;
  // If a space is part of the initial spaces that do the indentation of a cell
  // it is not eligible for soft line breaks: It would add a soft line break
  // that causes the same indentation to be introduced in the new line again and
  // therefore would not help at all.
  wxCoord indentationPixels = 0;
  wxString textToStyle = m_text;
  SetFont(m_configuration->GetRecalcDC());
  wxString suppressedLinesInfo;

  // Handle folding of EditorCells
  if (FirstLineOnlyEditor()) {
    long long newlinepos = textToStyle.Find(wxS("\n"));
    if (newlinepos != wxNOT_FOUND) {
      size_t lines = textToStyle.Freq(wxS('\n'));
      textToStyle = textToStyle.Left(static_cast<size_t>(newlinepos));
      if (lines > 1)
        suppressedLinesInfo =
          wxString::Format(_(" ... + %li hidden lines"), static_cast<long>(lines));
      else
        suppressedLinesInfo = _(" ... + 1 hidden line");
    }
  }

  // Split the line into commands, numbers etc.
  m_tokens = MaximaTokenizer(textToStyle, m_configuration).PopTokens();

  // Now handle the text pieces one by one
  size_t pos = 0;
  wxCoord lineWidth = 0;

  for (size_t ti = 0; ti < m_tokens.size(); ++ti) {
    auto const &token = m_tokens[ti];
    pos += token.GetText().Length();
    auto &tokenString = token.GetText();
    if (tokenString.IsEmpty())
      continue;
    wxChar Ch = tokenString.at(0);

    // Handle Spaces
    if (Ch == wxS(' ')) {
      // All spaces except the last one (that could cause a line break)
      // share the same token
      if (tokenString.Length() > 1)
        m_styledText.push_back(StyledText(tokenString.Right(tokenString.Length() - 1), GetTextStyle()));

      // Now we push the last space to the list of tokens and remember this
      // space as the space that potentially serves as the next point to
      // introduce a soft line break. pos was already advanced past this
      // token, so its last character sits at pos - 1.
      m_styledText.push_back(StyledText(wxS(" "), GetTextStyle()));
      // This space is the next candidate spot for a soft break. Only a plain
      // space qualifies: the tokenizer renders non-breakable spaces as ' ' too,
      // and breaking there would misrepresent where the content wraps. pos was
      // already advanced past the token, so the space sits at pos - 1 and the
      // continuation line would begin at pos.
      candidate = SoftBreakCandidate{
        m_styledText.size() - 1, pos, false,
        (pos >= 1) && (pos - 1 < m_text.Length()) && (m_text[pos - 1] == wxS(' '))};
      // The spaces belong to the current display line, too.
      lineWidth += GetTextSize(tokenString).GetWidth();
      continue;
    }

    // Handle a Tab: the tokenizer always hands it to us as its own isolated,
    // single-character token (never merged with surrounding spaces), so it
    // can be pushed straight through. Its width depends on where it starts on
    // the line, so unlike every other token it is never measured via
    // GetTextSize()/cached via SetWidth() -- Draw()/Recalculate()/
    // GetLineWidth() all expand it to the next tab stop at the point they use
    // it instead. Deliberately not registered as a soft-break candidate the
    // way a space is: wrapping exactly at a tab is out of scope here, a line
    // can still wrap at the next space after one as today.
    if (Ch == wxS('\t')) {
      m_styledText.push_back(StyledText(tokenString, GetTextStyle()));
      lineWidth = NextTabStop(lineWidth);
      continue;
    }

    // Most of the other item types can contain Newlines - that we want as
    // separate tokens
    wxString txt = tokenString;
    wxString line;
    bool containedNewline = false;
    for (wxString::const_iterator it2 = txt.begin(); it2 < txt.end(); ++it2) {
      if (*it2 != '\n')
        line += wxString(*it2);
      else {
        containedNewline = true;
        if (line != wxEmptyString)
          m_styledText.push_back(StyledText(token.GetTextStyle(), line));
        m_styledText.push_back(StyledText(token.GetTextStyle(), "\n"));
        // A hard newline starts a fresh display line: the width accumulated
        // for the previous line and its wrap bookkeeping no longer apply -
        // in particular a space on the previous line must never receive
        // this line's soft break.
        lineWidth = 0;
        indentationPixels = 0;
        candidate.valid = false;
        line.Clear();
      }
    }
    if (line != wxEmptyString)
      m_styledText.push_back(StyledText(token.GetTextStyle(), line));
    if (containedNewline) {
      // Only the remnant after the last hard newline counts towards the
      // current display line; there is no soft-break candidate in it yet.
      if (line != wxEmptyString)
        lineWidth += GetTextSize(line).GetWidth();
    } else {
      HandleSoftLineBreaks_Code(candidate, lineWidth, tokenString,
                                indentationPixels);
      // Having weighed this token (and possibly broken before it), a breakable
      // operator now becomes the candidate spot for the following tokens: a
      // zero-width break inserted just after the operator's render snippet.
      if (token.GetTextStyle() == TS_CODE_OPERATOR) {
        const wxString prevText =
          (ti > 0) ? m_tokens[ti - 1].GetText() : wxString();
        const wxString nextText =
          (ti + 1 < m_tokens.size()) ? m_tokens[ti + 1].GetText() : wxString();
        if (BreakAfterOperator(tokenString, prevText, nextText))
          candidate = SoftBreakCandidate{m_styledText.size(), pos, true, true};
      }
    }
    if ((token.GetTextStyle() == TS_CODE_VARIABLE) ||
        (token.GetTextStyle() == TS_CODE_FUNCTION)) {
      m_wordList.push_back(token);
      continue;
    }
  }
  std::sort(m_wordList.begin(), m_wordList.end());
  if(!suppressedLinesInfo.IsEmpty())
    m_styledText.push_back(StyledText(TS_CODE_COMMENT, suppressedLinesInfo));
}

void EditorCell::PushTextLine(const wxString &line, const wxString &indentChar) const {
  if (line.Find(wxS('\t')) == wxNOT_FOUND) {
    m_styledText.push_back(StyledText(line, GetTextStyle(), 0, indentChar));
    return;
  }

  bool pushedAny = false;
  wxString run;
  for (wxString::const_iterator ch = line.begin(); ch != line.end(); ++ch) {
    if (*ch == wxS('\t')) {
      m_styledText.push_back(
        StyledText(run, GetTextStyle(), 0, pushedAny ? wxString() : indentChar));
      pushedAny = true;
      run.clear();
      m_styledText.push_back(StyledText(wxS("\t"), GetTextStyle()));
    } else
      run += *ch;
  }
  m_styledText.push_back(
    StyledText(run, GetTextStyle(), 0, pushedAny ? wxString() : indentChar));
}

void EditorCell::StyleTextTexts() const {
  // Remove all bullets of item lists as we will introduce them again in the
  // next step, as well.
  m_text.Replace(wxS("\u2022"), wxS("*"));

  // Insert new soft line breaks where we hit the right border of the worksheet,
  // if this has been requested in the config dialogue
  if (m_configuration->GetAutoWrap()) {
    SetFont(m_configuration->GetRecalcDC());
    wxString line;
    size_t lastSpacePos = 0;
    wxString::iterator lastSpaceIt = m_text.end();
    size_t lastLineStart = 0;
    wxCoord width;

    // Is this a new line - or the remainder of the line after a soft break?
    bool newLine = true;
    std::vector<wxString> prefixes;
    std::vector<wxCoord> indentPixels;
    wxString indentChar;

    size_t i = 0;
    wxCoord indent;
    wxString::iterator it = m_text.begin();
    while (it < m_text.end()) {
      // Extract a line inserting a soft linebreak if necessary
      while (it < m_text.end()) {
        wxString::iterator nextChar(it);
        ++nextChar;
        // Handle hard linebreaks or indent a soft linebreak if necessary
        if ((*it == '\n') || (nextChar >= m_text.end())) {
          // Can we introduce a soft line break?
          // One of the next questions will be: Do we need to?
          if (lastSpaceIt != m_text.end() && lastSpacePos > 0) {
            // How far has the current line to be indented?
            if ((!indentPixels.empty()) && (!newLine))
              indent = indentPixels.back();
            else
              indent = 0;

            // How long is the current line already?
            width = MeasureTextWidth(0, m_text.SubString(lastLineStart, i));
            // Do we need to introduce a soft line break?
            if (width + indent >= m_configuration->GetLineWidth()) {
              // We need a line break in front of the last space
              *lastSpaceIt = wxS('\r');
              line = m_text.SubString(lastLineStart, lastSpacePos - 1);
              i = lastSpacePos;
              it = lastSpaceIt;
              lastLineStart = i + 1;
              lastSpacePos = 0;
              lastSpaceIt = m_text.end();
              // In this case we have to skip the it++ at the end of the loop
              // as we want to re-process the character at the new position.
              goto lineProcessed;
            }
          }
          if ((*it == '\n') || (*it == '\r')) {
            if (i > lastLineStart)
              line = m_text.SubString(lastLineStart, i - 1);
            else
              line.Clear();
          } else
            line = m_text.SubString(lastLineStart, i);

          lastLineStart = i + 1;
          lastSpacePos = 0;
          lastSpaceIt = m_text.end();
          break;
        } else {
          // We cannot introduce soft linebreaks since there were no spaces we
          // could break at.
          //
          // TODO: If we handled spaces before we handled soft line breaks this
          // branch would be unnecessary, right?

          // Spaces, newlines and reaching the end of the text all trigger
          // auto-wrapping
          if ((*it == ' ') || (nextChar >= m_text.end())) {
            // Determine the current line's length
            width = MeasureTextWidth(0, m_text.SubString(lastLineStart, i));
            // Determine the current indentation
            if ((!indentPixels.empty()) && (!newLine))
              indent = indentPixels.back();
            else
              indent = 0;

            // Does the line extend too much to the right to fit on the screen /
            // to be easy to read?
            if (width + indent >= m_configuration->GetLineWidth()) {
              // We need a line break. Does the current line contain a space we
              // can break the line at?
              if (lastSpaceIt != m_text.end() && lastSpacePos > 0) {
                // Introduce a soft line break
                *lastSpaceIt = wxS('\r');
                line = m_text.SubString(lastLineStart, lastSpacePos - 1);
                i = lastSpacePos;
                it = lastSpaceIt;
                lastLineStart = i + 1;
                lastSpacePos = 0;
                lastSpaceIt = m_text.end();
                break;
              } else {
                if (*it == wxS(' ')) {
                  *it = wxS('\r');
                  line = m_text.SubString(lastLineStart, i - 1);
                  lastLineStart = i + 1;
                  lastSpacePos = 0;
                  lastSpaceIt = m_text.end();
                  break;
                }
              }
            }
          }
        }

        // Remember the current space as a point we potentially can break lines
        // at
        if (*it == ' ') {
          lastSpacePos = i;
          lastSpaceIt = it;
        }

        ++it;
        ++i;
      }
lineProcessed:
  // If we fold the cell we only show the first line of text.
  if (FirstLineOnlyEditor()) {
        m_styledText.push_back(
                               StyledText(line + wxString::Format(_(" ... + %li hidden lines"),
                                                                  static_cast<long>(m_text.Freq(wxS('\n')))), GetTextStyle()));
        break;
      }

      // Determine how much which line has to be indented for bullet lists
      // or citations

      // Handle the start of new lines
      if (newLine) {
        // Let's see if the line begins with a "begin indenting" marker:
        wxString line_trimmed(line);
        line_trimmed.Trim(false);
        if ((line_trimmed.StartsWith(wxS("* "))) ||
            (line_trimmed.StartsWith(wxS("\u2022 "))) ||
            (line_trimmed.StartsWith(wxS("\u00B7 "))) ||
            (line_trimmed.StartsWith(wxS("> ")))) {
          // An "begin indenting" marker

          // Remember what a line that is part of this indentation level has to
          // begin with
          wxCoord w;

          indentChar = line.Left(line.Length() - line_trimmed.Length() + 2);

          // Remember how far to indent subsequent lines
          w = GetTextSize(indentChar).GetWidth();

          // Every line of a Quote begins with a ">":
          if (!line_trimmed.StartsWith(wxS("> ")))
            indentChar.Clear();

          // Equip bullet lists with real bullets
          if (line_trimmed.StartsWith(wxS("* "))) {
            auto posOfBullet = line.find("*");
            if (posOfBullet != wxString::npos)
              line.at(posOfBullet) = L'\u2022';
          }
          if (line_trimmed.StartsWith(L"\u00B7 ")) {
            auto posOfBullet = line.find(L"\u00B7");
            if (posOfBullet != wxString::npos)
              line.at(posOfBullet) = L'\u2022';
          }

          // Remember what a continuation for this indenting object would begin
          // with
          prefixes.push_back(wxS("  ") +
                             line.Left(line.Length() - line_trimmed.Length()));
          indentPixels.push_back(w);
        } else {
          // No "begin indenting" marker => Let's see if this is a continuation
          // of an indentation
          while (!prefixes.empty()) {
            if (line.StartsWith(prefixes.back()))
              break;
            prefixes.pop_back();
            indentPixels.pop_back();
          }
          // We don't need indentation as this line was indented
          // by spaces already.
        }
      }

      if (prefixes.empty())
        indentChar.Clear();

      if ((!indentPixels.empty()) && (!newLine))
        indent = indentPixels.back();
      else
        indent = 0;

      // Equip the last soft linebreak with indentation.
      if (m_styledText.size() > 0) {
        if (m_styledText.back().GetText() == wxS("\r"))
          m_styledText.back().SetIndentation(indent);
      }
      // Store the indented line in the list of styled text snippets
      PushTextLine(line, indentChar);

      if (it != m_text.end()) {
        // If the cell doesn't end with the last char of this line we have to
        // add a line ending to the list of styled text snippets
        if ((i + 1 < m_text.Length()) ||
            (it != m_text.end() && *it == wxS('\n'))) {
          // Store the line ending in the list of styled text snippets
          if (*it == wxS('\n'))
            m_styledText.push_back(
                                   StyledText(wxS("\n"), GetTextStyle(), 0, indentChar));
          else
            m_styledText.push_back(
                                   StyledText(wxS("\r"), GetTextStyle(), 0, indentChar));
        }
        // Is this a real new line of comment - or did we insert a soft
        // linebreak?
        newLine = ((i + 1 >= m_text.Length()) || (*it == wxS('\n')));

        ++i;
        ++it;
      }
    } // The loop that loops over all lines

    // The prose wrapper above writes its soft breaks as '\r' markers into
    // m_text while it works (it reads them back to detect continuation lines).
    // Move them out to the side table now and restore m_text to pristine
    // content: nothing outside StyleText() ever sees the transient markers.
    // Each entry is the zero-width offset just AFTER the broken space (the
    // start of the continuation line); the left-to-right scan keeps
    // m_softBreaks ascending, as IsSoftBreakBefore()'s binary search requires.
    // A folded cell renders only its first line, so - like the code path - it
    // records no breaks; the scrub still runs so no marker survives in m_text.
    const bool recordBreaks = !FirstLineOnlyEditor();
    for (size_t p = 0; p < m_text.Length(); ++p) {
      if (m_text[p] == wxS('\r')) {
        if (recordBreaks)
          m_softBreaks.push_back(p + 1);
        m_text[p] = wxS(' ');
      }
    }
  }   // Do we want to autowrap lines?
  else {
    m_text.Replace(wxS("\r"), wxS("\n"));
    wxStringTokenizer lines(m_text, wxS("\n"), wxTOKEN_RET_EMPTY_ALL);
    while (lines.HasMoreTokens()) {
      wxString line = lines.GetNextToken();
      if (FirstLineOnlyEditor()) {
        PushTextLine(line + wxString::Format(_(" ... + %li hidden lines"),
                                             static_cast<long>(m_text.Freq(wxS('\n')))),
                    wxEmptyString);
        break;
      }

      PushTextLine(line, wxEmptyString);
      if ((lines.HasMoreTokens()))
        m_styledText.push_back(StyledText(wxS("\n"), GetTextStyle(), 0, wxEmptyString));
    }
  }
} // Style text, not code?

const MaximaTokenizer::TokenList &EditorCell::GetAllTokens() const {
  if(FirstLineOnlyEditor())
    {
      if(!m_tokens_including_hidden_valid)
        {
          m_tokens_including_hidden =
            MaximaTokenizer(m_text, m_configuration).PopTokens();
          m_tokens_including_hidden_valid = true;
        }
      return m_tokens_including_hidden;
    }
  else
    {
      if(!m_tokens_valid)
        StyleText();
      return m_tokens;
    }
}

void EditorCell::StyleText() const {
  wxASSERT(m_configuration->GetRecalcDC() != NULL);
  if(!m_configuration->GetRecalcDC())
    {
      wxLogMessage(_("Bug: dc == NULL"));
      return;
    }
  // We will need to determine the width of text and therefore need to set
  // the font type and size.
  SetFont(m_configuration->GetRecalcDC());

  m_wordList.clear();
  m_styledText.clear();
  // Soft breaks are derived layout data; re-derive them from scratch on every
  // restyle. They live in a side table (m_softBreaks), never inside m_text.
  m_softBreaks.clear();

  if (m_text != wxEmptyString) {
    // Defensive normalization: a bare '\r' can only reach us as raw input
    // (legacy files, paste). Real soft breaks are never stored in the content
    // any more, so this touches nothing in normal operation.
    m_text.Replace(wxS("\r"), wxS(" "));
    // Style as code or prose, dispatched by the concrete subclass.
    StyleTypedText();
  }
  m_tokens_valid = true;
}

// Base = prose; CodeEditorCell overrides this to style Maxima code. This is the
// virtual seam that replaced StyleText()'s old `if (m_type == MC_TYPE_INPUT)`.
void EditorCell::StyleTypedText() const { StyleTextTexts(); }

wxString EditorCell::PreprocessNewValue(const wxString &text,
                                        std::size_t &cursorPos) const {
  cursorPos = text.Length();
  return text;
}

void EditorCell::SetValue(const wxString &text) {
  std::size_t cursorPos = 0;
  m_text = PreprocessNewValue(text, cursorPos);
  CursorPosition(cursorPos);

  // The old text's diff-highlight ranges mean nothing in the new text.
  m_diffHighlights.clear();
  m_diffHighlightsTextLength = 0;

  FindMatchingParens();
  m_containsChanges = true;

  m_text.Replace(wxS("\u2028"), "\n");
  m_text.Replace(wxS("\u2029"), "\n");

  // Style the text.
  StyleText();
}

bool EditorCell::CheckChanges() {
  if (m_containsChanges != m_containsChangesCheck) {
    m_containsChangesCheck = m_containsChanges;
    return true;
  }

  return false;
}

size_t EditorCell::ReplaceAll(wxString oldString, const wxString &newString,
                              bool ignoreCase) {
  if (oldString == wxEmptyString)
    return 0;

  SaveValue();
  wxString newText;
  size_t count = 0;
  if (!ignoreCase) {
    newText = m_text;
    count = newText.Replace(oldString, newString);
  } else {
    wxString src = m_text;
    wxString src_LowerCase = src;
    src_LowerCase.MakeLower();
    oldString.MakeLower();
    auto pos = src_LowerCase.Find(oldString);
    while (pos >= 0) {
      newText += src.Left(pos);
      newText += newString;
      size_t charsToCopy = src.Length() - pos - oldString.Length();
      src_LowerCase = src_LowerCase.Right(charsToCopy);
      src = src.Right(charsToCopy);
      count++;
      pos = src_LowerCase.Find(oldString);
    }
    newText += src;
  }
  if (count > 0) {
    m_text = newText;
    m_containsChanges = true;
    ClearSelection();
    StyleText();
  }

  // If text is selected setting the selection again updates m_selectionString
  SetSelection(SelectionStart(), SelectionEnd());

  m_text.Replace(wxS("\u2028"), "\n");
  m_text.Replace(wxS("\u2029"), "\n");

  return count;
}

size_t EditorCell::ReplaceAll_RegEx(const wxString &oldString, const wxString &newString) {
  if (oldString == wxEmptyString)
    return 0;

  wxLogNull suppressor;
  SaveValue();
  wxString newText;
  size_t count = 0;
  RegexSearch regexsearch(oldString);
  newText = m_text;
  count = regexsearch.ReplaceAll(&newText, newString);
  if(count > 0) {
    m_text = newText;
    m_containsChanges = true;
    ClearSelection();
    StyleText();
    SetSelection(SelectionStart(), SelectionEnd());
    m_text = newText;
    m_containsChanges = true;
    ClearSelection();
    StyleText();
  }

  // If text is selected setting the selection again updates m_selectionString
  SetSelection(SelectionStart(), SelectionEnd());

  m_text.Replace(wxS("\u2028"), "\n");
  m_text.Replace(wxS("\u2029"), "\n");
  return count;
}


bool EditorCell::FindNext(wxString str, const bool &down,
                          const bool &ignoreCase) {
  // If the search string is empty we prepare everything for a new search
  if (str.IsEmpty()) {
    if (down)
      CursorPosition(0);
    else
      CursorPosition(m_text.Length());
    return false;
  }

  // Default to start the search at the right end of the cell
  size_t start;
  if (down)
    start = 0;
  else
    start = m_text.Length();

  wxString originalStr = str;
  // Handle ignore-case
  wxString text(m_text);
  if (ignoreCase) {
    str.MakeLower();
    text.MakeLower();
  }

  // If this cell is already active we might already be at a suitable
  // start position for the search or within a search.
  if (IsActive()) {
    // If the last search already has marked a match for our word we want
    // to search for the next match.
    if (GetSelectionString().IsSameAs(originalStr, !ignoreCase)) {
      if (down)
        {
          start = SelectionLeft() + 1;
          if(start >= m_text.Length())
            return false;
        }
      else
        {
          if(SelectionLeft() > 0)
            start = SelectionLeft() - 1;
          else
            return false;
        }
    } else {
      // The search expression itself changed -- e.g. the user typed another
      // character into the find box. Re-test the search *at* the current match
      // (not one position past it) so that, when the now longer (or shorter)
      // expression still matches where the current match is, the selection is
      // simply extended/contracted in place instead of jumping to the next
      // match further down the worksheet.
      if (down)
        start = SelectionLeft();
      else
        start = SelectionRight();
    }
  } else { // Inactive cell => try to make sure we start at a sane position
    if (down) {
      CursorPosition(0);
    } else {
      CursorPosition(m_text.Length());
    }
  }
  // GH #2208: wxString::find()/rfind() return size_t, whose "not found"
  // value is wxString::npos (size_t(-1)), not wxNOT_FOUND -- assigning it to
  // a signed 64-bit variable and comparing against wxNOT_FOUND (-1) only
  // works by accident on a 64-bit build, where size_t is also 64 bits wide
  // and the bit pattern reinterprets as -1. On a 32-bit build size_t is
  // 32 bits, so npos zero-extends to a large positive number instead,
  // making a failed search silently look like a match at a huge offset.
  size_t strStart = wxString::npos;
  if (down)
    strStart = text.find(str, start);
  else
    strStart = text.rfind(str, start);

  if (strStart != wxString::npos) {
    if (down)
      SetSelection(strStart, strStart + str.Length());
    else
      SetSelection(strStart + str.Length(), strStart);
    return true;
  }
  else
    {
      if (IsActive()) {
        if (down) {
          CursorPosition(0);
        } else {
          CursorPosition(m_text.Length());
        }
      }
      return false;
    }
}


bool EditorCell::FindNext_RegEx(wxString str, const bool &down) {
  wxString text(m_text);

  wxLogNull suppressor;
  RegexSearch regexSearch(str);
  RegexSearch::Match match;

  size_t start;
  if (down)
    start = 0;
  else
    start = m_text.Length();

  // If this cell is already active we might already be at a suitable
  // start position for the search or within a search.
  if (IsActive()) {
    // If the last search already has marked a match for our word we want
    // to search for the next match.
    RegexSearch::Match selMatch = regexSearch.FindNext(GetSelectionString(), 0);
    if (selMatch.Found() && selMatch.GetStart() == 0 && selMatch.GetEnd() == GetSelectionString().Length()) {
      if (down)
        start = SelectionLeft() + 1;
      else
        {
          if(SelectionRight() == 0)
            return false;
          start = SelectionRight() - 1;
        }
      if(start >= m_text.Length())
        return false;
    } else {
      // We are at the start of a match, but the search expression has changed
      if (SelectionStart() > 0) {
        if (down)
          start = SelectionLeft() + 1;
        else
          {
            if(SelectionRight() == 0)
              return false;
            start = SelectionRight() - 1;
          }
        if(start >= m_text.Length())
          return false;
      } else {
        if (down)
          start = SelectionLeft();
        else
          start = SelectionRight();
      }
    }
  }

  if (down)
    match =  regexSearch.FindNext(text, start);
  else
    match =  regexSearch.FindNext_Reverse(text, start);
  if(match.Found())
    {
      SetSelection(match.GetStart(), match.GetEnd());
      return true;
    }
  if (IsActive()) {
    if (down) {
      CursorPosition(0);
    } else {
      CursorPosition(m_text.Length());
    }
  }
  return false;
}

bool EditorCell::ReplaceSelection(const wxString &oldStr,
                                  const wxString &newString, bool keepSelected,
                                  bool ignoreCase, bool replaceMaximaString) {
  wxString text(m_text);

  auto start = SelectionLeft();
  auto end = SelectionRight();
  if (!SelectionActive()) {
    SaveValue();
    wxString left;
    wxString right;
    if(CursorPosition() < m_text.length())
      {
        left = m_text.Left(CursorPosition());
        right = m_text.Right(m_text.Length() - CursorPosition());
      }
    else
      {
        left = m_text;
      }
    m_text = left + newString + right;
    CursorPosition(CursorPosition() + newString.Length());
    StyleText();
    return true;
  }

  if (ignoreCase) {
    if (text.SubString(start, end - 1).Upper() != wxString(oldStr).Upper())
      return false;
  } else {
    if (text.SubString(start, end - 1) != oldStr)
      return false;
  }

  // We cannot use SetValue() here, since SetValue() tends to move the cursor.
  wxString text_left = text.Left(start);
  wxString text_right = text.Mid(end);
  SaveValue();
  m_text = text_left + newString + text_right;
  StyleText();

  m_containsChanges = true;
  CursorPosition(end + newString.Length() - oldStr.Length());

  if (replaceMaximaString) {
    if ((newString.EndsWith("\"") || (text_right.StartsWith("\"")))) {
      if (!((newString.EndsWith("\"") && (text_right.StartsWith("\"")))))
        CursorMove(-1);
    }
  }

  if (keepSelected)
    SelectionStart(start);
  else
    ClearSelection();

  if (GetType() == MC_TYPE_INPUT)
    FindMatchingParens();

  StyleText();
  return true;
}


bool EditorCell::ReplaceSelection_RegEx(const wxString &oldStr,
                                        const wxString &newString) {
  wxString text(m_text);
  wxLogNull suppressor;

  auto start = SelectionLeft();
  auto end   = SelectionRight();

  RegexSearch regexSearch(oldStr);
  RegexSearch::Match match;
  match =  regexSearch.FindNext(text, start);
  if((start != end) && (match.GetLength() != end - start))
    return false;

  SaveValue();
  match =  regexSearch.Replace(&text, start, newString);
  if(!match.Found())
    return false;
  m_text = text;
  CursorPosition(match.GetEnd());

  if (GetType() == MC_TYPE_INPUT)
    FindMatchingParens();

  StyleText();
  return true;
}

wxString EditorCell::GetSelectionString() const {
  wxString retval = m_text.SubString(SelectionLeft(), SelectionRight() - 1);
  return retval;
}

TextStyle EditorCell::GetSelectionStyle() const {
  size_t pos = 0;

  if (SelectionActive()) {
    for (const auto &textSnippet: m_styledText) {
      const wxString &text = textSnippet.GetText();
      if ((SelectionLeft() <= pos) &&
          (pos + text.Length() < SelectionRight())) {
        if (textSnippet.IsStyleSet())
          return textSnippet.GetTextStyle();
      }
      if (pos > SelectionRight())
        return TS_INVALID;
      pos += text.Length();
    }
  } else {
    for (const auto &textSnippet: m_styledText) {
      const wxString &text = textSnippet.GetText();
      if ((CursorPosition() >= pos) &&
          (CursorPosition() < pos + text.Length())) {
        if (textSnippet.IsStyleSet())
          return textSnippet.GetTextStyle();
      }
      if (pos > SelectionRight())
        return TS_INVALID;
      pos += text.Length();
    }
  }
  return TS_INVALID;
}

/***
 * FindNextTemplate selects the next template
 * of moves the cursor behind the first closing
 * paren in the current line.
 */
bool EditorCell::FindNextTemplate(bool left) {
  static wxRegEx leftVarsRegex, rightVarsRegex;
  static const bool leftVarsRegexOk =
    leftVarsRegex.Compile(wxS("(<[^> \n]+>)[^>]*$"));
  static const bool rightVarsRegexOk =
    rightVarsRegex.Compile(wxS("(<[^> \n]+>)"));

  wxASSERT(leftVarsRegexOk);
  wxASSERT(rightVarsRegexOk);

  const wxRegEx &varsRegex = left ? leftVarsRegex : rightVarsRegex;

  size_t positionOfCaret = CursorPosition();

  // Splits the string into first (from caret in the direction of search)
  // and second (the rest of the string)
  wxString first, second;
  if (left) {
    first = m_text.Mid(0, positionOfCaret);
    second = m_text.Mid(positionOfCaret);
  } else {
    first = m_text.Mid(positionOfCaret);
    second = m_text.Mid(0, positionOfCaret);
  }

  size_t start, length;

  // First search in the direction of search
  if (varsRegex.Matches(first)) {
    varsRegex.GetMatch(&start, &length, 1);
    if (left) {
      SelectionStart(start);
    }
    SelectionLength(length);
    return true;
  }

  // Then in the rest of the string
  if (varsRegex.Matches(second)) {
    varsRegex.GetMatch(&start, &length, 1);
    if (!left) {
      SelectionStart(start);
    }
    SelectionLength(length);
    return true;
  }

  return false;
}

void EditorCell::CaretToEnd() {
  CursorPosition(m_text.Length());
  if (GetType() == MC_TYPE_INPUT)
    FindMatchingParens();
}

void EditorCell::CaretToStart() {
  CursorPosition(0);
  if (GetType() == MC_TYPE_INPUT)
    FindMatchingParens();
}

void EditorCell::CaretToPosition(size_t pos) {
  CursorPosition(pos);
  if (GetType() == MC_TYPE_INPUT)
    FindMatchingParens();
}

#if wxUSE_ACCESSIBILITY
wxAccStatus EditorCell::GetName(int childId, wxString *name) const {
  if (childId != 0 || !name)
    return wxACC_FAIL;
  *name = ToString();
  return wxACC_OK;
}

wxAccStatus EditorCell::GetState(int childId, long *state) const {
  if (childId != 0 || !state)
    return wxACC_FAIL;
  *state = wxACC_STATE_SYSTEM_FOCUSABLE;
  if (IsActive())
    *state |= wxACC_STATE_SYSTEM_FOCUSED;
  return wxACC_OK;
}

wxAccStatus EditorCell::GetDescription(int childId,
                                       wxString *description) const {
  if (childId != 0)
    return wxACC_FAIL;

  if (description == NULL)
    return wxACC_FAIL;

  switch (GetType()) {
  case MC_TYPE_INPUT:
    *description = _("Maxima code");
    break;
    //  case MC_TYPE_CHAPTER:
    //    *description = _("A chapter heading");
    //    break;
  case MC_TYPE_SECTION:
    *description = _("A section heading");
    break;
  case MC_TYPE_SUBSECTION:
    *description = _("A subsection heading");
    break;
  case MC_TYPE_SUBSUBSECTION:
    *description = _("A sub-subsection heading");
    break;
  case MC_TYPE_HEADING5:
    *description = _("A sub-sub-subsection heading");
    break;
  case MC_TYPE_HEADING6:
    *description = _("A sub-sub-sub-subsection heading");
    break;
  case MC_TYPE_TEXT:
    *description =
      _("Comment (ordinary worksheet text that isn't fed to maxima)");
    break;
  default:
    *description = _("Bug: Unknown type of text");
    break;
  }
  return wxACC_OK;
}

wxAccStatus EditorCell::GetDefaultAction(int WXUNUSED(childId),
                                         wxString *actionName) const {
  if (actionName != NULL) {
    *actionName = _("Type in text");
    return wxACC_OK;
  }
  return wxACC_FAIL;
}

wxAccStatus EditorCell::GetValue(int WXUNUSED(childId),
                                 wxString *strValue) const {
  wxString retval = ToString();

  // If the blinking caret is currently visible we hide the char under the caret
  if ((m_displayCaret) && (CursorPosition() > 0)) {
    if ((CursorPosition() < retval.Length())) {
      if (retval.at(CursorPosition()) == wxS(' '))
        retval.at(CursorPosition()) = wxS('%');
      else
        retval.at(CursorPosition()) = wxS(' ');
    } else
      retval += wxS("%");
  }
  *strValue = retval;
  return wxACC_OK;
}

wxAccStatus EditorCell::GetFocus(int *childId, Cell **child) const {
  if (IsActive()) {
    if (child != NULL)
      *child = const_cast<EditorCell *>(this);
    if (childId != NULL)
      *childId = 0;
    return wxACC_OK;
  } else {
    if (child != NULL)
      *child = NULL;
    if (childId != NULL)
      *childId = 0;
    return wxACC_FAIL;
  }
}

wxAccStatus EditorCell::GetRole(int childId, wxAccRole *role) const {
  if ((childId == 0) && (role != NULL)) {
    *role = wxROLE_SYSTEM_TEXT;
    return wxACC_OK;
  } else {
    return wxACC_FAIL;
  }
}

#endif

/* const std::vector<EditorCell::StyledText> &EditorCell::GetStyledText()
   {
   if(!m_tokens_valid);
   {
   StyleText();
   m_tokens_valid = true;
   }
   return m_styledText;
   }
*/
